BIND 10 trac2225_xfrout, updated. 79a11d19f140d1420bc9d83dc1fbdcf907319418 [2225_xfrout] s/dump_statistics/get_statistics/
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Jan 21 05:31:29 UTC 2013
The branch, trac2225_xfrout has been updated
discards 514cd120aa0ac15c4e2118255d6176e90add211b (commit)
discards b541596415b3a79c9ec5e7e2c32b2acb1a9be28c (commit)
discards 9ca7f37f2fc5464446a38a7ebc9256b71fb8944f (commit)
discards 17ace8323f227b86f9b3c376ffb0cdbd86075710 (commit)
discards ca8b0515f7c638d9697b608965f18b5d1c5b5fa0 (commit)
discards da7349e2f4f79a781365a4198f9425865c729d1a (commit)
discards cb9e424c61fc7b42038fd2c5d49a22b3ca581a01 (commit)
discards 52ec102b16ae6969c4aa405ec6617765336d69f1 (commit)
discards 4a1c77d95ad8c51b4a47a379b273809e8e8e2621 (commit)
discards b2a12049d34b061dff5027e9a78e6a00d751c773 (commit)
discards 12ed470e0c12f4879c5398a52c7364e2fe4ce01c (commit)
discards 657c926eaa761b234d648aa4eac99e7f99a4f1e7 (commit)
discards 8a0613d11028d6eba72e08dd7d44aa08bcc682c2 (commit)
discards 9de118b1aadb88902755c18ff7b584ceb84ac545 (commit)
discards 32c40f54a0b1e833d8b03cfb43da064d39a7591c (commit)
discards 1112a612b9df64344416a10730df3ed843b3c5de (commit)
discards 58ce07ad5ba3d527384ca88a5139f5a6faee004a (commit)
discards 7025d308cd3f2a68cc370835ba059b3a43051fc0 (commit)
discards 8770765db4becde5c0347a055dd7db7bfd6120c0 (commit)
discards f4c26bfac4daf9520d189259d0380c5fa814e0ae (commit)
discards 1b87787e20f6333959235623754fbebae0445e43 (commit)
discards 83c439eff853360e633b6e539c6d38ff9c49e971 (commit)
discards fa4cd1a315d67c2f48f28bc487035bbe44f291d9 (commit)
discards 34900ee9027ff21655fbaca820696f10a69b2f83 (commit)
discards 7c1af32c1336e61c9546f8cd564ff9c660ccf203 (commit)
discards a165423b537cf5771ea810dba64215a724050a5a (commit)
discards bc8dde5aaed1b519e26a20b383cddec07c01369d (commit)
via 79a11d19f140d1420bc9d83dc1fbdcf907319418 (commit)
via 7390503b44baf97dc67ddaf800cd82b28f097d65 (commit)
via 5072f7f444a75c7152ed74135629748b4f71f584 (commit)
via eeaced2980d16428a1c523e1a0c77d7c7bd0ecb0 (commit)
via f99dea73ed9b124b72000b57f8ba8856f5838e9b (commit)
via 620caae79d9fd369f3872c82540a8ed80066bf12 (commit)
via 8013bd184be4669ec53274d1b174bc119a5a73b3 (commit)
via 7fbc8323cd986bda0e74941d76af727fb5b1df00 (commit)
via f8220e01112ddc39844db6bc69b5b66113f83641 (commit)
via 8b9c751d904bfd58f8be4d56e7c168c421794fc2 (commit)
via c98f15811814caf7bdf407864b88253f7311ecde (commit)
via f90889b6559cb2ba95a33108df1388afb5a12e7f (commit)
via 6392c36a0df416bf4476e2d5d3427105cbea98d7 (commit)
via 38896504bb750809442c77599b0c859a111b9789 (commit)
via a0033b21f7c50fe62b954bc501b8381e409966e8 (commit)
via 7016d0a7f2015d23d67b51cb594613a5ed619eba (commit)
via a2f8dd5d40c8c6c06ceb7278e6b8990f2a13e7dc (commit)
via 78a13fe9f10d8f2b982564e480ff375316dcf098 (commit)
via b3b2d10880821f7a434c32a0f3a7aabb6a1e5bf6 (commit)
via 6f6247afed0403bd27e9a70116445973750a646c (commit)
via bf26e302febe956fe4216ba1f1f3fc4590255090 (commit)
via b2c11d14542767ab608cf03da71e8f8332fc370b (commit)
via 365e3472c5ea96f3458097ba87f4270972c6eb8d (commit)
via 8733e86c9483dc5008c54ef37bac0e0455e9b877 (commit)
via c06c560406e56d49868c1fe593f5e8f64a00006f (commit)
via edbe6dfa75648a21b426a6075ccfe7ffc4ec2caf (commit)
via a81cba650f8324317dec95b4d95652521a5baf63 (commit)
via 0a89b374d57877e3b1ea1f379276d74a34ea7d8f (commit)
via 95d6795825d9df9634d2282b28d1d9a5761b6634 (commit)
via 5204b8af562627419798146f71f7402849bfaedc (commit)
via a70f6172194a976b514cd7d67ce097bbca3c2798 (commit)
via 0a433790559aa0e0fe148a06bbe50d9df522312a (commit)
via f2c0ce1adf8090ecb9ed76e6f3f201d0b044e060 (commit)
via 79b57c7becdf6fa66812c75bf29a63f63221442f (commit)
via ced31d8c5a0f2ca930b976d3caecfc24fc04634e (commit)
via dd637a386748c2a393acc3d437c071a10b9cc7f8 (commit)
via 3aee00b231be08a7edecfedf833ab0d9f2629922 (commit)
via d3c90ab38ecda07729741cb67505c4e9a659c6be (commit)
via 980d715bc1b4b6dc592ac6c8e7bdedf4cf528f6c (commit)
via 0f244147145019810ae16b36383352cbf4672919 (commit)
via 9423db0d25e30b0a3e1334b56c0ae754675bb2ce (commit)
via fe931e2f1c65e3bde969aa3bb0a2a031201fab95 (commit)
via f3709cc11e26e2bb2a0cfa4f447efce885160276 (commit)
via 39512e1b3142a1006cb793817691f4ce9fd7480c (commit)
via bdc99366fe025e5175e4a6e2c189e3d5ae1125f7 (commit)
via b88f6fb05d83b5ee7b7594630e02093398f995c7 (commit)
via 24e0b47e82d29460f4dabfb66558204db9b3b83d (commit)
via 2992f269ed62c67a5fb60ad60c0f7dcd89fba957 (commit)
via 2f061fe969a7af07cecd9cd5d166f5b2d5b327aa (commit)
via 6fbf5830da2a9ed728a865715bab0d59e1a5431b (commit)
via eda46b3f2aa8742c7ee7798383d59b401a07bc0f (commit)
via c84dafee22cf9c39e1dde4d15c62246ec25fc09a (commit)
via 48d999f1cb59f308f9f30ba2639521d2a5a85baa (commit)
via 61a7a86532aac5a0b84cadee0330f0eb20762ba6 (commit)
via f9169a71cb277c3733a8ff7d0fea87762196762d (commit)
via d47e9e008a7eb572f370ab7b5527c8188bb24017 (commit)
via 7d4b55818c8510a180889b157bfb360ef601af1b (commit)
via 71e25eb81e58a695cf3bad465c4254b13a50696e (commit)
via 65eb1db45415266f4c3c435b799f066eaf96998b (commit)
via b0a6e5fff9ad673e6ba6a55797ab08b1349ecd8f (commit)
via 2283402c2b94545fa66cbc5e8f7294cb39924568 (commit)
via 2f3611a364d4ec7b2626921c22029b8dbc024404 (commit)
via 6a1247051d30f4eb5be35b661f74a90a51390cbf (commit)
via a7872f60cba423597e3049aa8acd466fbe29882d (commit)
via 973bfad40ba4fe946d53d200c7e27ab32d79d0de (commit)
via 275a72e95dc82e105e48654e1a3ed71a48b7840a (commit)
via 13674dda1cf3f4f16964c7d5fc72559e3d90b8c2 (commit)
via 5a326976867ae6a6f4c98d056b40d36ff1dbac22 (commit)
via 914eb9b64bfcf703c2538cbf20b9c82bfbdfb525 (commit)
via 8d31e215ffc21430b29276ed65dd44a57598f9b8 (commit)
via 1cee583601a4e084bf54cf90e0d52b79f0783700 (commit)
via d09c436d4bb61e4ac7e3a6c2f3836ac30b29c40d (commit)
via 09c47d4fef3388cd1aa2aa7db402da67c4968d3f (commit)
via 48892724360a9c706de8c2ffaa5099dc1da211ea (commit)
via 2b05b3926ac85363b1063a12c9ac8d0ac9cda3de (commit)
via 0c28935d20983b280976f0a62e4b32de735b29a8 (commit)
via 568655c55d104fcee40289810dd4431188a989f1 (commit)
via f2f653c6f29d56c00fdb09af6ba54bd1ab36f31f (commit)
via 8bd08bf2531ad91b4d3f4ef4b8f304c2dfc9c249 (commit)
via caf2b100bf61d75cd98935bb59b6b39a718289a9 (commit)
via 75d921323cf83bf67ec24d28927ee472b517fa88 (commit)
via 715a6f61177570c411f2e63f82ae1b2269a654cf (commit)
via f45c804082f5ebfbe3339224083531fe86f2bfb2 (commit)
via 5eeb4b30cbea6d0f5588cf0ad542cdccd49510c1 (commit)
via 84ba4a728f827325e262bb91c2517f45efef5d7e (commit)
via 3b38bc023ff63f7567ebce3d9a1cea9fc8654d1d (commit)
via 27369e24c8d4acca1ab5905245b218e31747aee8 (commit)
via 804f48610620ea057b91857ed6d3c993f796c444 (commit)
via 329ab8b7d40b2bd34997da2635cf68e51795cbf3 (commit)
via 37322e5124dce0d69305553977cbcf30e659688a (commit)
via 4b9b3bdcb7d97d916248215846002b27d91c43c2 (commit)
via 84a994f8ef9bef873fd36a0837476f7b0a2d319c (commit)
via 7800a308cb8db2193f8406e1ef4db1f7f6dd0332 (commit)
via cc7b41e7ce61e745602748346c4b00b8b864bdef (commit)
via ac0eaa438f7b76f1f58d54070313e9ffc5417c26 (commit)
via 9a0d024c97ba896c577d59db04a0337d3c70221a (commit)
via ccf170eee20359d3661fc70a7ce02b965cc90a69 (commit)
via fd3de5ebc7bbc021ba4b36769f4541a1fad5fffc (commit)
via 76c52a9395ff0b7bcd9b911308d43da8171a7cd5 (commit)
via 7d705b35fe149cc0c6b93211f2733537b05bd905 (commit)
via 016a19585f32b9bdf57fc5aecdc3b073f3d1a482 (commit)
via 7cbed6378d88f921cf33b12d18d78e56400b4702 (commit)
via a3e0f04e9f735501a2ef08b7d3c0de3f22849fcc (commit)
via ae4b62b7f869cc7e66ef691ab20faff93566ac5b (commit)
via d7d8d1bf1a37b33891f09a2b3641c9bf5231d19d (commit)
via 38439b178eeddb7148df3da5ebab555b294cc967 (commit)
via 579f5abd033f56e772c6ee0286c58634d12aabfe (commit)
via 867d9a39ee23b4e02ef9d36d241688f0ad525e5d (commit)
via 1b32af4e5cb9349d34b468cd0333bbcfdb338d5a (commit)
via 58df436a73f5b3cbe6177aea37383c3ec00ca76a (commit)
via ca0fc023a12228a9ebc20e90921f8ab23ec3274b (commit)
via 30e71e8bfdb6049265dca14a8ce62b989fe67118 (commit)
via a032d84ef41ca563063fa15ad34250f87c05eb65 (commit)
via c3edda086f948bc9c6bd46c8b9198a2e5261eac2 (commit)
via 761ece75fb3184fc28ddedcb742cf0c94fb5ef5e (commit)
via bd51648e6e9c8eac8943db455e48b26c8b196b14 (commit)
via e906f116b8125b58fefb62a14281bbf36b2d7941 (commit)
via 8e5e30b800b786cbcf8ff12db820bb1a7c75d69f (commit)
via 9f079c662135d45fac7be5b0b9282e7542c55e81 (commit)
via 2f5ba8955ec66c1c7af60b85272f03383b256fc0 (commit)
via 61e7fa26cfe5ec6cd4a381bdc9e5bc25437f2620 (commit)
via 2f7f7b1e0fb86c93c17b04489ab7e836a59fe622 (commit)
via b6d00d5fb3657b555c9f25d4c23721d277a7d7a4 (commit)
via 68709a07c59441cd101e750d72049a6218f0262b (commit)
via 019ca218027a218921519f205139b96025df2bb5 (commit)
via f09298e6e1512b32462f41a4ced3dcc1950e18aa (commit)
via 54cbb54f358dd83e6c87f23ed17c5df754a28dc7 (commit)
via 4baf3a4dcacaa5d2fe57dba1a8cb1eee68cd0f21 (commit)
via 6e2a0ba86f0607f3e9822a178a223e8a922de47a (commit)
via ec9d85afe154206e95b683eaa8733e0f453f4090 (commit)
via 0b3abc02d6b943026d5e60f6376d5c20d913bcb7 (commit)
via c9d46273b40b9be9902deaa081be0f1216d287c6 (commit)
via 6278daf2fc808fd4a311a97529815db1ef216e79 (commit)
via f8fb3f6f2f6f42888be0cd4e8c9df9502c944081 (commit)
via 29e27da67a1558281a52a5a1c233db3bf84efcb3 (commit)
via d86e308e8d1cf3c569f9ad1edf17bb4289da954c (commit)
via 9bc254508724e55db2ce9624a0a9322f4d3d9f31 (commit)
via 00980e1e05152e7b44968438dc846ea41f43c113 (commit)
via 355fb4b538030ec31101edd4b8ea6e2ae2d90e65 (commit)
via 60606cabb1c9584700b1f642bf2af21a35c64573 (commit)
via 34724a6251f8d624c2c69d06fc1ddb9c21f201f0 (commit)
via beef799a018be9b0be103973c9fa477a7d0ca6fb (commit)
via 5200f31617133fc3700b802a7299efbc3a980ec5 (commit)
via b82c412af1ef36000368f817f25aef23cc36728a (commit)
via 360a5bb6cb72862edbbe9a4cb0074360f1385e21 (commit)
via 6478f4a1f3d3fb66a7fc25887135bdf3403538bd (commit)
via 924221ee423612e1389ff98a88ddc127e283aa02 (commit)
via 7a8402a585b391394949582c14ffb9182c757979 (commit)
via 82a1d5c34e951687d67a6313fb42d9e75768adbc (commit)
via ead15091870f4dbd5a87640be3493e36d9e9da73 (commit)
via ecfc245797cd10313557991c778e686fa1f19b37 (commit)
via 1c54eac8eb70d3fc7c16b83a1269315e710021c1 (commit)
via 79cfa55b6202c07cbba0b0db6532ca2dff9e2a9c (commit)
via a9a2f80671f01894e3c2909e8a185c8fb7396977 (commit)
via bed3c88c25ea8f7e951317775e99ebce3340ca22 (commit)
via 2d95931bbb2efb3ae30b6fe7219958e40b51c4f8 (commit)
via adf283b9aa964639693d23a4e809fe67657583fb (commit)
via 4d277fcbe264e299596a2ddc15243c87565f6127 (commit)
via 84bc0b71b8c31013d702c206a3fb1b4d1b93e514 (commit)
via e567eee7d939fa75ac7ccee625178e179e47ad98 (commit)
via 3072c500b4c563cb22b7cdae805cdb6c1e99a408 (commit)
via 76ca31298b2e7f5d85904045aaa6560bcf7ff8d6 (commit)
via f603440ab46a86436b2208697f36bd9726fc0e03 (commit)
via 11b14c65cb4c01d5d5278f14ac5b292842f91e13 (commit)
via e81f84bdabd3a5b7370bd2f4c41d9c7de333fafd (commit)
via 989e0535b0a1b2746aab98aa00aef995eeb4696b (commit)
via b32cd0ea8778cac7a9f9e991d3c978dc7faf93cd (commit)
via a095d57b989aca503c22bfe2121df389abdbd392 (commit)
via a8e4e8678710187bbd044689a0ed603f5182fda8 (commit)
via 5477d36770d789ce36b2a74acedf64cf63e79460 (commit)
via 905a40f2a1c627edd9bbaf26166311ba7e623d0e (commit)
via c924b23383294fe5b7a21aed00e62fbfd8e88d1c (commit)
via f000b6b54d4abfad7abe39ed0b817a29a1f073f0 (commit)
via 6b78fee487016e4920ab469448c559752523d474 (commit)
via b8b0b9c2b4c0ef559c027033fdcb95e8ff61d9fb (commit)
via e4f44ed5418cca60318c89472defcf24eea93826 (commit)
via 137cfc9037a103e5c5ec814a798dbc56db6ae835 (commit)
via db45e761f5f8c140d80d242033642e2d929a2a2d (commit)
via 36d56446c3758b1d8e6c005fabfe8432b9751131 (commit)
via 2bbe63dbbdda8e75fa60df6ea19f4b0354edebeb (commit)
via 2dbb441195792e760669119ea99e945f66a71a69 (commit)
via c4904fef78c9bae9fdff9aa9852d7b8dca3a9ba8 (commit)
via 4209615da0b85838cc481a7ad602d7783bfdd78a (commit)
via aff266bfb0c5ea0d8553fb249aacd91f4f414476 (commit)
via 30bb4f426cc06433dbfdbad4d6080ff9ab80ca10 (commit)
via f8dfc94a4a5941def5a4624f7755c0478b8d6375 (commit)
via 5bfa13805e9a9ee2585426ddd8807175eac1a6a3 (commit)
via ba7d30083a692af134c9b4c52b92b7856b34096a (commit)
via e18d03ada70eab16d6f5855b1bf95e78167c4c82 (commit)
via 0c40d70c28cda1bc61871cd0e26b74656f45114d (commit)
via 37a27e19be874725ea3d560065e5591a845daa89 (commit)
via 3ea93cc724f87d47c024f895650853f284daf9a1 (commit)
via 5e3bc4cf78dddd89f3a9e4670336b7d46bbb124e (commit)
via c16bfe0dc892c2bd34e88daa12120202bcf9e3eb (commit)
via 6c0febf6e8ca20b9d585d5b5577ee2989464fad0 (commit)
via 716eeec4d6fc5d5db9a8dba8b242fd541380a92f (commit)
via a69acbd2f9dde5eb533b95ef0d538495e733dcd2 (commit)
via 173f076fb4c6d687de433786e0cf1ffd53fb95b2 (commit)
via 88639855c40c5a935126bb44d49883e4f452740b (commit)
via 7174cca8bf02759d3947f2bdc5437e11c362d2a7 (commit)
via 042731613c48d83d96bd3a4911b397977ae783dc (commit)
via 6926ec8a661213e78e73922d093c9883a5d16eb1 (commit)
via 331f01d140ccc6d2f9a37fb483581bfdac1f5cfa (commit)
via 79007cc52b34b9cca6259c86a479ddc5100f15f5 (commit)
via 26a6ff11a1634de2624080eeb424df4d782496f4 (commit)
via 6010b776a5a76c7c9df5025e86c7902992750615 (commit)
via e39b61088924933e1335760d21cfa10e0cfd5425 (commit)
via 88a70cf0dcc476dd911e812eaca03e61274f81ac (commit)
via 421343439bb3852a4aea4d9f9ff945a4b8b6ac9a (commit)
via d365482e0e132c96812b7b99c465582a027035f2 (commit)
via 533c4afdae8ff16eb7d7b1f5529dd572e1098cf3 (commit)
via 21794c5df650ecf28f55ed95cb12d278945ab3a5 (commit)
via bd9b58b2875c100b68e3f246a09975bde0a13e16 (commit)
via 4e973e228e15b24d70b36c18f79180833aff1839 (commit)
via b3a9b362643eddb59ef78ff392cf091d0c199163 (commit)
via 92db29d982892679b7ef097f55975357c5abc759 (commit)
via f8ad67f7166f71b5f78721cc2d7df85c2a859bbc (commit)
via 8dc51055585e92db23cfb97fd280a10eecf3ff7a (commit)
via 18adb5bce11c191f14aa26e58fca838aeb9d80d0 (commit)
via f4e6ab2a98e80e8832b201a6ed2db716bd1d4ee1 (commit)
via ccd941541d51c3a3eb3df162e094637ad8317f63 (commit)
via b25df37f16da91f53b2e068fdd5aeb78e349311b (commit)
via dac912f20c6f315f951979906e79c9a47ea172fe (commit)
via 1dc55b22817baca2b9750082dbf408209affbf02 (commit)
via 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8 (commit)
via d5310a9cec9a3ece6b0cb323ea2a933caca127b9 (commit)
via 564f603d2056a823b3436acf0183a9abeec35ecb (commit)
via 856d60a96bbed5735541659d7c12c51fc055a6cb (commit)
via 792c129a0785c73dd28fd96a8f1439fe6534a3f1 (commit)
via b85908b8e8321ae1b1ab139c7182a5026bc7f73d (commit)
via 3e191cf2889f9ef77561ebac18e1cc019d1bffe4 (commit)
via b789222291eeb63126dc1c249ed89d4f4517db63 (commit)
via 6e4caf9dd4f4239776d1e05efbfe13987e5bf9a5 (commit)
via 2548205bf705e3f2d9aeb75e35eb112206476d03 (commit)
via 8399e2b6d96d18197dc27b9f4bee1f5051f6c7af (commit)
via 3c162ca506c8fc08ab93374521807b2132e81c24 (commit)
via 5eb2ff86d745543185551758bf31132596ad20d9 (commit)
via 0de32e7fe21e18069d26e3d282ac8078c852f004 (commit)
via 70cb4d4886e27554e9a9e8751089ba8bd4678701 (commit)
via fb10ec4e31a94ed4c98fa83c5ef3064f8e53a39e (commit)
via 83e71312ca3631e5a8f2364bb8434d69ae000ee1 (commit)
via b55b8b6686cc80eed41793c53d1779f4de3e9e3c (commit)
via 7234eee9cf529a98841ca502dde655bd15f4299f (commit)
via 806865cd2e830785f0d377d759e7475b9e1f640b (commit)
via e7f9ab53656fdfb8a1b27c7056f8dc2c86d8f966 (commit)
via 35d02013f29d19fabf678089ba97db2e67ab3f25 (commit)
via 004823862f69853d3c4acd6f8e0bc60925c25ed1 (commit)
via 1fa69473d881a5c31cd802563f2c398e1f6d1ecb (commit)
via 3baea8cf898f2e9dcd35c88e48999b8112732f9d (commit)
via 08c2f82000bacb76d0602cc9c428637aed0baf21 (commit)
via be570bcaf88318286027da7da40a600cbe4223e9 (commit)
via e5a354694f4d0192132d00e25223d16eb24f25d7 (commit)
via a92495cd29fbe405bef744b98d01ff200caa05c6 (commit)
via f516fc484544b7e08475947d6945bc87636d4115 (commit)
via d509593229c0424fc430766f56d5e5947cb3dda8 (commit)
via f1e9ef1f4906cfc43cf76308675d24a851739105 (commit)
via 96e05ecaa1e2c19c28ff5d1f50efdcff7e3e7d5c (commit)
via c6f04a39f70ad71571779b734e7a3593e6c2f69f (commit)
via 96f973347163030737690174068f51c193a2f4e6 (commit)
via c85c06e93473453547d4a378ebc3c12ef74ae68c (commit)
via 202d9dd4ef7d116f844029a34c36fbdb48921ba0 (commit)
via 18831888667fe9363c22d3d19db9b0f98e56ef70 (commit)
via b0a3df2d384689dad61a47084b5dfa5a00918885 (commit)
via de2886172153001a862453ee4ed73bcd07db2ae0 (commit)
via 58c136da23483524f053d3caa20210941bfa13db (commit)
via cb50f4f6d306ebd40d7ff03b1494dc52b96b191d (commit)
via 7cee8494a729694402e9ce19d257d643888b7a4c (commit)
via 15a69fd77a14825ca0deb1cba52690bf0ea33195 (commit)
via 50e76dc960ad76ec97004832d718cb337390fce8 (commit)
via 3e9a69424117d8f9841d76cf71dab353b6aa5435 (commit)
via 890e0b0b7df76e61c1638744647a793143537722 (commit)
via a6ce7e4332223277ca50cd68a65e7e404621717c (commit)
via d83fc2a12f36818bdd60c87e61d01be7a5dfa4e5 (commit)
via 46efce68984c6ab8c5d02618e2b9ca21bf46de06 (commit)
via 7bea86f90950a22f72d0c6271d7086ab59abdfa0 (commit)
via 1f011110511b06ca175f19fecf0ce522140c6f95 (commit)
via 9e85dc2c98c9fde0f1d73c1d6c0d0d6188b41439 (commit)
via 36622df85e6b1b7ce4fc621fbfaa21d2b35003d8 (commit)
via bf592d0607a639ff454dcdc820a76ae0eaacbbf4 (commit)
via 2ed60b95b8cbdff758e08062fc82c8d8f7b367ac (commit)
via 059fcd024b1611a7c5ca52a6f7055f7c9d14f106 (commit)
via d078381e41cf375a3504655ec921999828c284e2 (commit)
via c6e109f7d68347703baa542d0a913e97394b8057 (commit)
via 9fbb0ae95365974d80a87704f64d6e4907dcb52e (commit)
via 4cb0974a3a642d06513ecc43743109bd92e45a08 (commit)
via 573d2b1fbc3145d18ec71621f56e5c74a328ed3c (commit)
via d831b49beab09fe65b95d39858486824e9b9a4da (commit)
via 2d107bd1f98f6a3aed2120213d713111246e6535 (commit)
via 57e0fe723d9769cb893ae168f7d9374280670c4d (commit)
via 28f38670d37fe76c2481df6b8f82284ce4949de2 (commit)
via 9a2fd23cdd58afe5dbc932dead8f10b0ac22605c (commit)
via b8e5414ee0445b3b0141203470ac07f42d7bbc32 (commit)
via aa32f38e127d1b3b3d4e1349fbfdcac155dc5c5e (commit)
via 40502d5e2c6cbe71143e842ac7908e8a55b62324 (commit)
via c6981a0eed4cb0c68f747fa7f758e53dbfdaf5f7 (commit)
via 385f156f420aa17a1d6f07bcb17a87075f9d4492 (commit)
via 0fe0d44af6e81f2e5230515237fcc84b42f37d85 (commit)
via 60b979b7088f9e282b173c8d71792e2c4b20b57c (commit)
via 0974318566abe08d0702ddd185156842c6642424 (commit)
via 31147f0f82fd1e1b8ead164153599269822517ea (commit)
via 8679b4053024172db8e3d55b7ed50a855efc77c0 (commit)
via 5530bff261fd921726fce87c2d296da5271c639f (commit)
via dd444334eb873bd925639e3141c3a863fad1e47a (commit)
via c1fd5c76dc679f0156748f66f58f3d7220703332 (commit)
via 234e86994e8af3f68d830ec22e9857aa34c78983 (commit)
via 5d3af5f896df69f42590921a44650745411673fa (commit)
via bbb8a8f0ed42862b7e5b54e7611cb0d0e364203b (commit)
via ee424bbc727b754b8030fb2adaa69c578f096de9 (commit)
via bdf19add5fca40dbc91c6ce4c0d87d8bb6080bde (commit)
via cfbb9eada0a6e0ff8a2faead73f870d3ca3400fc (commit)
via 8bca74f57a81fa7e2dc2569692b253dbf19270b4 (commit)
via 34929974f3655f19029d83234e4c5b89b85eaf99 (commit)
via c466b2fb0a4888db16ff0888dc0f8c9875d6f43c (commit)
via e2f99db35b161d951e60aee4728118cf41c3b3d4 (commit)
via f47a693087d2583eee59f97e9b43c40535443688 (commit)
via 9faa2925c3934e3526b5b24694ec0986a6303d42 (commit)
via 469a7104b7231ef26e9f08e85e9f4acf035398c4 (commit)
via 4d7307519b21a0364350cbac4560f6693430cd6b (commit)
via 86f542bb1fecca6e8d4b93a3462649b9dc17805f (commit)
via a12ebb8fdcf246c1a096d62c624185c96f88b186 (commit)
via 0fd48fc7f5406e8cbd8b459a7fc5231053519d3b (commit)
via df8823fdb350670a1e5333b335b59a56e8b09b4c (commit)
via 3e100854e0a85d215d614729f6d429340be1ead2 (commit)
via a146bf1575760b55554d3d77520ea0900b9a4d51 (commit)
via 1deefe2736bcc2b61e07f20ea732d178d84ab772 (commit)
via 6c7d6aae42af702f8b846b3984c09854a5a58539 (commit)
via 09e8ac90d96a468beb509c4da6e9dd74975aa1ad (commit)
via 20419496af5c9374c7858251e1b857413d1f7d17 (commit)
via 3965fb5302e46eb4e7763a328add0a83687a8309 (commit)
via 810c95b11f458b18c196a39cba8f3d47ac1d6cb2 (commit)
via 85da7d68f1eff606608bd46df43a8f57436a0131 (commit)
via d4eda56344420aafa49d1c0d91d1a6a95c7a4707 (commit)
via bd8ac0ce36dee77dc4865239576745db7588db60 (commit)
via 98d515d5e653a75762b5da424555ac301e7f9af5 (commit)
via 4a15ebc6f38bf2b2fe66a069a58f22f89e8ec6fc (commit)
via 8f149bd04986ba2542e86c7ce77e5d0e92df16ea (commit)
via 201672389bfa78c8eb59e122f6f117d1eb0644ed (commit)
via fc882313210b117eeec371b33796a54a4094c1d3 (commit)
via 82d0b7f2ab2fcca6daafdcd5aae06cc0efcd2ffd (commit)
via 2205f81d523ba1175614c6a7ee54904b74ea1a43 (commit)
via a9c8b8f3cd7ae57265efd4e01c51cbb4526c038f (commit)
via da5c7b1faa2363bbca6db163364e6e0073bcc3ab (commit)
via 823f41b44713d161cbba7456914387e323595220 (commit)
via 0e90f478241569b4c63e86eea31553973bdab394 (commit)
via 0232cbe00f15efbe750b21c80973feb220bdc6c2 (commit)
via 67eb8ca010320d7c942ba154d70456d8e9981fa7 (commit)
via 0b722ec0d7813e933db3919df492dccf11a3b4eb (commit)
via b0732ce0447db537b3d643735f16aad09ddd4c7c (commit)
via 1c2b85d7d1c1448d78b8f2a9927b7952ef42e39d (commit)
via 6d3fb27de20503a07333bbc9e053451a80442a41 (commit)
via bef01123fb5dfaee927bc58da465109240811a18 (commit)
via 62794b031c00451e7bec0a0b94b4a2dadb42ae0e (commit)
via 82041829bd8cc7400d0fee9d08e4cf1091c25a70 (commit)
via baa1ddd9057d0f10f85faa67f92e94268189dcd1 (commit)
via 5379cabb61c3daff05c541ab6c9675a8a3ebfed7 (commit)
via 30cbe355d7fcccf3e06472e5fd04c9de870adbe6 (commit)
via 2629140f12accdfa6b4de4f59e5a76b0cc10ecb6 (commit)
via ea8082a263cb6d2b69764da2322b2c483913e5da (commit)
via 6c20066d5613dfda246d598f47b9e779eef9c70b (commit)
via 2aa422106dd081138c633ad2f9a3ede0ebdbd94a (commit)
via 14220fefc56f7970d6b8bfd316c7ee234dd416d4 (commit)
via 34596a05508cee3e48565a4082e9f6e9b815ad41 (commit)
via 6b045bcd1f9613e3835551cdebd2616ea8319a36 (commit)
via 32e13fbffae1a721a44f8b764ecb5ee066e64bf5 (commit)
via 2d46b704c62b47c2313d80b5afafbb180c1b7853 (commit)
via ae8851eea4d83050ad2a8e17fc6b8f0b11364fdd (commit)
via 2078a3a34f198220806f9eabcf6fa7f6f0c9288c (commit)
via 5ee9971dc60341992cf7da807dbec32811dd14d7 (commit)
via 025db8d7a207b39174362981868742f1b80bdf69 (commit)
via 00845ee8d7366b80e23e349b7cf8cd9d52551785 (commit)
via 5d2a22ac8f92c53a89a5f4507ac3946529b9d49e (commit)
via 4323bf82f07269a4f99a951b659749a5a1234ef2 (commit)
via fb95fbd37114f9c8fed764979cc5dc9124e3439c (commit)
via 96e4248d61ccbcd23c7563f0a36d5443b607ea02 (commit)
via ae5693b89371208d5d5d8e884b52362f861c633f (commit)
via 778e6009722520325aaa1efc98240fffc8f6b6d1 (commit)
via 59b176e95540aacd2b0f167cba350d7fa18771db (commit)
via b52e736b9e5079aedf5252f04d019a80292bfc41 (commit)
via 9b0a73f8a2523480f538747d579c940534ac5ac5 (commit)
via 36fea252b03a38a0a86b830bc6a3373a88966413 (commit)
via d4bd5936f52d20ba21865858861dc358f531da04 (commit)
via 19f2ceb60860badfc183f9d4e7d54ac9105ee296 (commit)
via ce6444b69feb909e5c8212e7335eeca14a2b11ea (commit)
via 33ba1827b513aa735a483f6927667a9e872a2207 (commit)
via 23035066d0c07c7b83120d3cba9efc1febe18ff5 (commit)
via 92e4f34d7c74ff7b34599e1d3e8d0622ebc633d6 (commit)
via c2e9d347dcd2fdb0f9092dc4b04579d1c412de46 (commit)
via 90f997d7b29824aa848beb73745e5009c415956d (commit)
via 080d47deb3088adddd96434acfd1c82bd14779c9 (commit)
via 9f5102a30e4cb8da4697b3561206957b9a2aae05 (commit)
via 0758ead7f4044e0867d4f953427b6c10f7afab45 (commit)
via 9ab72138d43cb21a620c021a24e6a248b3dd77ab (commit)
via ffe973e3df51ab36e17087eda23b1042500d39b5 (commit)
via 857d7fbf031d29b5b66ebe38fec725dde35008fd (commit)
via 514b87c307fc834f39f0be155fc9bac9a6508d70 (commit)
via 6d28690c67369927bb0806618f23533a1457eb6f (commit)
via 029be672c7bf1b936859e811a17d45d04215ecd4 (commit)
via 1a0577f9f348a74eacc959e091756f0e7a71accb (commit)
via 5ec876c98d10dfdb0675ffcde709b5ce9871465b (commit)
via 09dc37b8319af571d2e7afb0bf5b6da82cd174f6 (commit)
via 12b24545cb9aeb86229694cda68c71577587f125 (commit)
via 0a14dac0417402193dbcd50cb73360aeef9aabb2 (commit)
via 9cb41a2f06b35290e72eccc8246d8abf57f6e929 (commit)
via 7383457001cd2b6d78ec755f1cf20fa4529a43ab (commit)
via e6c05e218034a214844e1d4ab8b8c1060ec05489 (commit)
via b5735838a44da418d7d66724c8bb53c2d4d1b3c7 (commit)
via e4a823ad343ed077f88da1e581d0486c9b634a35 (commit)
via 6c738ab17c6b6632f1a607cea6cea480fd1c72aa (commit)
via 7eee12217563565248904b7051a6d68f2374ebe0 (commit)
via b5aa1d1b12636ba96e3fff48e27fb6acf6e38333 (commit)
via 2fc0ce200dfa85a5f7baa577750b3c3fe8032f7d (commit)
via d33dd125553aa9702e1a29a6c0e30da9ffffe53b (commit)
via e17ddc8b0f6726a185e982c22dbf787351c22cdd (commit)
via 8960a571bc2db7129d4ce8861f83808fb71dc81c (commit)
via 3ff7531f6be864ca6965a60ef1e2744e945a3569 (commit)
via 786673c6998d886be73950a99a0292344b750cea (commit)
via fe40dade09da3ca910f034e650db2a3d5a93a49b (commit)
via fb8433b523f854b3978ef1c868d573e172a5a76c (commit)
via 5d2f35c760be8fe9aa54f566461ca8a9c5d0db78 (commit)
via 5d8d1e296a4eccc34ab0c19acfa9a4f39a63745c (commit)
via 08dec0f8e0129ed43373dca3a335d752a3fb5e2c (commit)
via 6ed60d209f8c6d7d7d4978b19a2feb6170e2c67a (commit)
via 448686bfad7694acde8b08355dd5921efd4bb2ca (commit)
via 5deb6bbc2df15dc20f36727f0abeaa4d4ad0c17e (commit)
via e44d06354aa0785cf5bd247358e59f7dfe69f5ad (commit)
via 17cd3618c21b12d7867d9e5e4dba81c1cee46cb6 (commit)
via 376b3b0760c5198dbf5ccf740c666ed9c9acaf45 (commit)
via 4beebdd9209a2a75b33068f4d1811b2664b957db (commit)
via db55f102b30e76b72b134cbd77bd183cd01f95c0 (commit)
via cb9f017de088330bd6e4107bad642b5e4ea66ce9 (commit)
via 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb (commit)
via daf78976a3df91027c2a82cd0a794255b60507b0 (commit)
via 291591772d83ff6b5ec9412b03624d2c9f13dc82 (commit)
via 28a43fa59ffd4f772104a76e3636b8c54b329aa3 (commit)
via 9065335de2f05d3bc1d874b933ba80b51feb68f6 (commit)
via caf95c197aa4abb916d8e27d3a5004ebed2e6108 (commit)
via a32568b7b3006315e79ee411b50db8cc4dc435a7 (commit)
via 3c702b8966081eb2d73914754abd247350957bdd (commit)
via c645605f43d14ed37a22369c3f5ac177acdeb7cd (commit)
via 8b1a0145b79147510ccde00b28e5124b8951e4d1 (commit)
via 19bd1a5a0b222ac2473793d87539f76a34cb1bec (commit)
via 27e7210b344cea5c7098bf233481d5f2bf7cac97 (commit)
via 230078e5f45f29fec72e35a77e2fd897fb6d3fb7 (commit)
via 77b42eabd7630b5ba7f5f9fa383de73e0e7fe49d (commit)
via 31a4bc5aacc85f79416c6c4da2e33e642077e9f9 (commit)
via 89f331dff577268f90d885dc871cd2a152940ef8 (commit)
via c6c569cae91e19aa6267081ef9926f52b87d903d (commit)
via c8dcb23d366d559cb685b64514f49d7222df3335 (commit)
via 3ac15510bb80c1f7fe76779dfa9ff254eb5cc796 (commit)
via e1c2233fab06d393eed6e16fc09f9bf672963112 (commit)
via 5f8c941e31630163287b5197b6185dae9b03de13 (commit)
via 17553447e571eee4690ad90caf8365977d57c4f0 (commit)
via 5054d0ed85f4645a8019e77c860e7415042e6d05 (commit)
via fb95fe7735c5995f727ea42dfafcad820a3764da (commit)
via 61c303c8f15dce6a985e5d0164a50844b6c64558 (commit)
via 6eb4014782afae7660dbdc291c315a211e5ceed5 (commit)
via bdb56ee0da86148611e50095c9d70dfe4e4f4b7f (commit)
via 17641de21f0430981b554af5595711a3bcf2d699 (commit)
via 2039944b9b5a639d61466c197660a81f047013b3 (commit)
via 2fa6a93d7f49f8073bc919620afc2cba4c7c0ce3 (commit)
via 357c297f73a57539c9b90f7f80c4bebbdf2f8db8 (commit)
via d8a4ee26573da0832ad2efc424e6c9db9760fb85 (commit)
via c661badf19c5e9593d8c4ba719b86cd96b85f787 (commit)
via fed1aab5a0f813c41637807f8c0c5f8830d71942 (commit)
via bd8497df6615f58b2a6de701cf2a8f02c4d57d9b (commit)
via a5aab271f44cce29af4344ae8c24da02167deb05 (commit)
via 57b40ee87d9689e6479d7fe4a2cf9903db3a3644 (commit)
via 2156cc8503238162e22f418632837003593c8e3c (commit)
via 61386c685763040bfcb779f0224bf6867a4dceb9 (commit)
via 44bd04d531d1881d7c9a8fea9bce4b6c10aea16d (commit)
via ef19026395320447f81738be3fd840e278ff8da2 (commit)
via 7299bfac202b013e968ff19c1c3109d78745ed22 (commit)
via bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2 (commit)
via f64ff7004ddec6c81538b2fc5d6827de6b79cfb8 (commit)
via 8d844f040f2f764f7be009c17d25b06e159b1924 (commit)
via 4a4642ad16734522436855cfd8e4d09c5ce32d06 (commit)
via a878a8107d48c5b1c476930cd4eb769f609fd609 (commit)
via 8474c4bf697ccf594ca3d08dd7b1f327a44e5e82 (commit)
via 71c5b61bcf71fb8ef3382569ea0eb059b0cca430 (commit)
via 667d94d93788e24158ebbec640f006f49b7d438c (commit)
via 15f662d68bbce471df63609cf8deccb280670048 (commit)
via 96c1a3cc7becc69e4bb24cd3e40aaa605e3d90e3 (commit)
via 774a4996dafc7a5809ab0ae08851d412e996e970 (commit)
via c864447b8ec187264fac032be1561c77bb3103b6 (commit)
via 95483cb529b42683002528186eb29d6854e7ce0e (commit)
via 8d8605b657d5e554f0c496143fe1a3b3cb8d3ea8 (commit)
via ff7903d22a1cc553ff22f9a6047e590f14c7dd32 (commit)
via fdce9eb73133bb52609ed6ad51fdb77b28447eb3 (commit)
via 689b015753a9e219bc90af0a0b818ada26cc5968 (commit)
via 0b4fca9e95537b24fa5a7412c998d424e6f567a5 (commit)
via 364716d15dc738cea57c0698e99ed8692ead0c9b (commit)
via 824581101796ab6aa13ce856f778d00ccb710345 (commit)
via 3b4124f6899a8d45195e03c319252b324f14c3c9 (commit)
via 8ee51ea2b44d0c9704809d84a55c2401cfe39853 (commit)
via fee6f278985f5112cb4d3601bb4c7287930d27ec (commit)
via 7740087adf45a16b1681e69a41bcb09ca590c3b1 (commit)
via 70e63e4b4fb5544ca2c8b9ced0100dd73712e847 (commit)
via c9d7464aee7f2392305d6f7ea24a318a9b0b8523 (commit)
via 619c53ae796b76c30bd758095c723bc9835c13c4 (commit)
via f91f820e5615292888269a06b502374f3857aa39 (commit)
via 37cc046b9fc52e7e01e903fb821548f3cec0f2f3 (commit)
via a51b040470e32b6e30447a605f25dd2977a5476e (commit)
via 2b23275bd5730b146658e3bb1f426a815030694a (commit)
via 6aa012341cc32ff61e67d82960aeaa07ce352fa6 (commit)
via b0bc2d360163be4126f6a88b73a3c17a18714736 (commit)
via 772ce4c2888583e2fe1e9959c705608f6d1b7e98 (commit)
via 00ae760dd4d30c5944dfb70a892c15004a1f07ee (commit)
via 94cb9c2b880da087af403da4b5b765ccd77ad594 (commit)
via 7e61e0a0c0d3b87a7e1033a353a273b1502caa8d (commit)
via 9394663ad4f49908d99dc2d2a47ddc0a72871f4a (commit)
via 1b69710afed58b9f7ebff61229f3f29e487f4026 (commit)
via 4d6495aa80132d3a67ad4ef9aa76d75683772a80 (commit)
via 530e62ba1a41f63a8dbcd0838fb5cfa95cb3816a (commit)
via ed7622df90ee865b5be03b0d13ed5977466bebfc (commit)
via ddd815eb743887a54500fdd839e2de17571b1bf1 (commit)
via 214e0409215d2dc329fb4b54e3f97d2f262ad375 (commit)
via e5a42900088bf1403bdbb43ed8b9894109478973 (commit)
via 72beaf8964363734d4f5e7b4b11ae7b8b4b5557c (commit)
via a489ad14ad44a593195b2f50f35c4825258a176f (commit)
via 62a23854f619349d319d02c3a385d9bc55442d5e (commit)
via 3449632270ba83f6d2114028e82f6f4c5098c2dc (commit)
via b07567633570ebffebb13e92ae71d429967a7cb8 (commit)
via 9c7390b2e75ff45d42f18e8775998ec5513c0053 (commit)
via 46cf8771c26b220b60ca8f30332442b5a7dbcbef (commit)
via 593eacf16fe69fade5eeb71704ac25c6e046421b (commit)
via b5904bd8eed8e36d1d6fd6d4305f687f53fe6733 (commit)
via bdc390376f18c7a858744eb9fb11827e792c6243 (commit)
via 393c70b7a308f5d816a87cf21e017351dbf09260 (commit)
via c980c37cce2a445ec40f2c526cd89310cf7c4941 (commit)
via 44f6dc45fffe9a44d8700bbb601dab169568f7ac (commit)
via 4114ac95b51b5551e38bc277860abf14775cc817 (commit)
via 55e0fb24720f98d51eeff77af6bdc0dff8649fa4 (commit)
via 7454de81c9a382b84361fe8e35a19e1d04890141 (commit)
via ee7c565a4d00c5ccac5f22858b430aff836550f2 (commit)
via 5cd3fa2a026c0cfadffe57b6c859f8621c17c21f (commit)
via 16bdc3898aa70d2145872a5277f60711c32f2bd9 (commit)
via d2cc8db8c12a44199fdfcb5ffbcda82794c1c189 (commit)
via e4be11b7e2f072f484fdd6eae589b41688e7de75 (commit)
via c4cf36691115c15440b65cac16f1c7fcccc69521 (commit)
via 655395cdae670d648bb556467773c4014094cce5 (commit)
via 92ac02b80cb94846eff4cd136d5c426526f5145a (commit)
via 2a8f04d9e437cc209ebb0921d0269ac4fdf3c760 (commit)
via 07a0de0905f93bea39de048b3db8ed2fe24ef0a9 (commit)
via 437477418c413c8a56adcb59441984dd5399f8a5 (commit)
via ad24f3f000a06f18ee1dba28ea2b38f8512f2c82 (commit)
via fbd4c46f6c801a5a662d28e75ed3f5014c2ae112 (commit)
via 2c49df942a07bdcdf335a5794e779a1fc0f870c5 (commit)
via 93cbc6cc8a27e8dad8eb0ef10a66655e7fbf6101 (commit)
via d7a0286809c9759de8f33209823eaddfe3f76264 (commit)
via d87cdaba29e56dd4b2f8db30749f04a66fa70d43 (commit)
via d1f88a92073bfe44db618ecf0b88a05c5dd79aa4 (commit)
via ed47bd72388da8de0ab5e92d44da12942ba49b35 (commit)
via cecd1c59612f853434cea629f19a4b4a234da966 (commit)
via 8b616d0d3fbf1da3533c9836687f7fa12a7da1c6 (commit)
via f277a67977c700be9e205c6af2c29f2b0764b19c (commit)
via 862d60332f64f7bb357cf8f300fd0faf610ef638 (commit)
via b9fa6ce6e6ba2d491096bc5c632e80c70a73614e (commit)
via 2aa24772e0d3c790ab2127933159747f30bd5430 (commit)
via ed49f68cc90984e065b40a0100e154956403fb35 (commit)
via 62f943fe6baa7a99990856bb03be039e54341ad3 (commit)
via 525af02fc521561f117064cfee06288798236287 (commit)
via c8f37fef053d256ba32adc921cf677059f61117f (commit)
via ea8e70a330e7930e3be85fcb6fe6ac7ca44fc643 (commit)
via eda75ca3700979153bcdee08b7d97a10f14519f0 (commit)
via 625f08940bce63bd70bd9d8dac67d003519a61f1 (commit)
via b40cd6851ace87bc97a095cf76b2a0ddee3666ed (commit)
via 16cc994d2f3e15180fc022976062d616b0c05f38 (commit)
via 89f2d7507297c5e3c1daf815f45e54499d029e27 (commit)
via 6f27e6fc9819b5807703ff141e31a0d7a6800a26 (commit)
via a435de0e81e1cde5ad40f5a8699252d31a2a39b7 (commit)
via a602d28cbe49a1287a4beb85ace69c854d025b02 (commit)
via f74f25e7b04afb0bb116fcb6023837e47f471a77 (commit)
via 5fa05448d68be1c4aafc3806ab0fc3302cd32597 (commit)
via f5dc7406f61fb182aeed500fd39c1fd3b486d6c3 (commit)
via 91455fb76e9acd98a1201f5e261896993e7a2339 (commit)
via 870ee769dedab453f6fb65082884cd2b45d1ca14 (commit)
via 2ad9f33c18a74df23e3ca10fd5246289093e2768 (commit)
via 079e629bdec110c1b07376645361fbcb2e467043 (commit)
via 165e2775424f0377b5eb8f574d83c0d0dcd67a4b (commit)
via 66c563e695df604afe0edab5d52b7fd4f2a871dd (commit)
via f8f73869c12d0cc0007618e91b943feab7073840 (commit)
via 00fd5f949667517a3d02c70b36476f430c0721c9 (commit)
via 90094d0add9f2047b2dbd307f800d4fb9ccccd73 (commit)
via 50a73567e8067fdbe4405b7ece5b08948ef87f98 (commit)
via ca8707b6528b0779ce9cc6de42169d53af561b8a (commit)
via 5a34019b3d874b4e8d1209d47d38592281b971ca (commit)
via dc3f31bea1c264125464906dae050a995829e816 (commit)
via 45596b6bd556fd78555930c4e883a5025912ec4d (commit)
via 471c6f2d6c1ad9b71422f1d19b6223492c5b6023 (commit)
via 3adc5a448824ec9a819c52c89869855cf1de8d0c (commit)
via 694dda8c2b939cf29d8949693f40dd12439fab0c (commit)
via 34cb2bf92262addf0fb401365c23773dee3f3e89 (commit)
via 23a5c8f5a97538d51389303ebef46164f95cd317 (commit)
via be2fccd2405459f3b421d2f7a7bec11103710f4c (commit)
via 01496b7eff80566dd62b4d308ac8c932a16ed13e (commit)
via db92f30af10e6688a7dc117b254cb821e54a6d95 (commit)
via 757d91ff52158a17e9e17990aa39815fda2ed3f6 (commit)
via 7400eee5cb287236717e78c8956cc09efb424813 (commit)
via 88dd03a0064c14a1608b6be9e4cff5d5cee7643f (commit)
via 8a49f520e191d8ff14d8118ec80ab5c83701995d (commit)
via c41a723a55a2d5665ee28716b32983ea17a38d97 (commit)
via 1a7c1e3d9a33115839c64869a28f3c7daf63efc0 (commit)
via 78688fbf0ddeb0f2e47a4988246b8ad29fe14ba4 (commit)
via 09d7669d9da37bcf44c914f378715f71e633180c (commit)
via 5efe6393d73425aedd8a9bdb8b4288dac0f1d358 (commit)
via 663bd7310eadfe3164d7504c1eceef716eb1086d (commit)
via 7898eed5a8cf9888d02dea2e3a7519b6148a8efe (commit)
via c2d65ae44cf2c167de0b6892359b72e54d8b81ba (commit)
via e3b18efc1d76c65fe1bdec7d6eb4821f3745cd52 (commit)
via 8b805bcd111bfeca94c98f60bd629387fbc7d342 (commit)
via 935c918578e428b5d24718e2e113dc706ac952da (commit)
via 0785c84f4f92d3aba3be9ee88a431ec7173929fa (commit)
via 9d1e869ba98529030a65359ecaeb2273cd2cb14f (commit)
via 0f4a4a3b12d7a1730bbf79079495eb397a72de08 (commit)
via 9e7a7d316106c60fdd3e3ec4ff85f7d3a10035a1 (commit)
via 605e81588c922ad5e93f87b56da722d79075993c (commit)
via d08ad5667f5b4fe540a1a00921e8a10d706cd88f (commit)
via 790d0b5527a8fd832f7236e17b4ac1274b865901 (commit)
via 8e839a08dc0c54d88c6e5a93b2be00f8eb81c2f9 (commit)
via c55cdfd6318072c9ae7005298fca07615065c97a (commit)
via be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
via e61c82ed57bfd7d5a0b25404cc1271a95b44c82c (commit)
via 7450f285f169e7ab894c5b85e303d1065795529e (commit)
via 4643f0af180b2c360a53a8d47660a92332e48aa9 (commit)
via ed8a874e740a95f78eba3f4a133b83794490dea7 (commit)
via 95cc8210a2f620d6a2a886cf7577ed2d257d2bad (commit)
via 52f3401fb59545555189a456f98fddbb2001b66d (commit)
via 3fb09910a4c862fef0e8f3dc631df0fba49c8107 (commit)
via 94d0481e3dbbc16f3178bcdb71e4b02af6ef85bb (commit)
via 3d8e49eb1d45b2a70b5ecb90d77116653a26b11f (commit)
via d7368c1d83dc9c6cbbfd4230efa03858307062d4 (commit)
via c9a786613ef05879fd0bcf88e6db0adf855e6ea1 (commit)
via a8b060aa956d679e7a4330450c9baf7e470ad6a5 (commit)
via bb85c71585b1bafb659287dba976940d37178630 (commit)
via 030ab7aacd96bf18bc1748cd30fecb9b4f60b890 (commit)
via 6ce0831748423ab48611785f29ee999beeaf8322 (commit)
via 32b6b02bc06bbf0303639964f3544e9cba4b138d (commit)
via da3bcafa328abd1bd6125f89a3091196954ef55d (commit)
via 0ebb7cd975d578f3dea516b790e502fdd65cb683 (commit)
via e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
via 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
via 318530edbf905ff288f527ef73bb88da4c57b548 (commit)
via 3759773a821827277aa2c8d457dd6fe63f1daf75 (commit)
via 79b7e9ce53c8bf6fc47285795599630cc9dc3ef8 (commit)
via 13f6a9e896862d5ca4aab33386a4c47c6636fe4d (commit)
via d0f80f2a66545a7247e97e7a17c26ae812349d26 (commit)
via a651d9b90dc0bc9737e6f104d1fde7f4b7c2110e (commit)
via a8c08c6be23f58204300bb2c916fcda7aef7b4c1 (commit)
via e47b08e8a356c1bf498aa5a615fcce86958f1fe0 (commit)
via 8fd5cc452a30a35df0a24b7bd1c30e76ea6bb54d (commit)
via 7cdae45e8e18a190a5d2097655f33c0684ef9b9f (commit)
via a7e16e85fac60062df06be2f371868571d08e5ed (commit)
via 0568e990cdd6304326274092234786b82ec61437 (commit)
via 3b50bb10b7afea442ad30f344c1138b67f7badeb (commit)
via 9e2d7b9b5f05168b165fb8d87a9c3d1466260dd9 (commit)
via c4af7dce242b7a4cd03d97fc27ede32c9d046b3f (commit)
via 974e63e70778f147863990b81be45eee9609921c (commit)
via 8693df3c6e3ba9e04f0a4f5d0f70646980986985 (commit)
via b3ea9e70fda28527deb8ac9b78c5ad253666ba0c (commit)
via a8ac0d4064ef4984b2327c5d27b743d257bf3ced (commit)
via 5f923b7b3a02bb1335b551e584647f3e7d4c53de (commit)
via e7f01efd3518de220064f1eda65bcb95221cfd3c (commit)
via 0333782cc1f51e1839e5cea59c72ef72a4b05929 (commit)
via 1f339724101df031a6e5b0338bbd085132f0e1ac (commit)
via 48a559472ac908be3bf697b11c01fb1a0fe13273 (commit)
via 8daf69ddf18e902d8e2758ba33538ff5edfbb44c (commit)
via 8652d1d4c6e2f9538b20443eb0cd0f37672915ef (commit)
via 72fb34ade6d092d35809280d3b6a25f6a800b778 (commit)
via 259634a0145c978f78f87cf84724553473e109fc (commit)
via 99c3c7f2457327d843c7f8d1e1b5adcb2f826373 (commit)
via 52b93e86bad0bc578336b9fc15e2a76c79dbecb9 (commit)
via ac36820764982483b3b25a2d2ed6e05cea6cf322 (commit)
via 6bd0c1d9f2f14339f83ce9fb1882f42ae23036a1 (commit)
via 6260781b99d5b7c1f495e584abe9f74ea4e33e03 (commit)
via 2c565f3b83fd09baffbeb82a7e8a4c56990f45dd (commit)
via 65ce062ac130500177a0adc4100289b8879c601c (commit)
via ea9d025cbcd9a318a2946c1a7f00283885ff381f (commit)
via 153bda11f4773ba8123c66d4a2d498a18a7b686a (commit)
via 6b7ef74b531830333300edd110fa2396630dacaa (commit)
via f104a60a20ac0fd402461dbf3e5d94fca14ba81b (commit)
via 5d7e273a02e5f5a1f83db02260a2a22e729f3a10 (commit)
via 57ad11888879be61a4d877b5ea4a29d504263e90 (commit)
via b25d6802e2236e06c9eb83a9508cb9bfca048563 (commit)
via 634b1cb2a47760b880e3fb6aca5a30cfd0602871 (commit)
via 98d529881bbb26780812a812cf0d1916a7195406 (commit)
via 75effbfc1dd4d69ae829974dcbf48c9d9c13580f (commit)
via baa520eb355a55fa9c9459d2cd4cd705533345f9 (commit)
via 2a98f3d106983b1e917af84747c089c5d8708df5 (commit)
via 0140368ed066c722e5d11d7f9cf1c01462cf7e13 (commit)
via fecfe82bdfc93fedc24263a8f1bbdcedf59a7b71 (commit)
via 588ff1eef555b3362954296388e93cf8729f267b (commit)
via bb1f38e03fb01fb164a58d91bd96bc9fcf4c07df (commit)
via 58664e2ecc10b71b6ce0126ccf8c5f336f7cd380 (commit)
via ce492ecf70bdd3fe483a7f4282e98443f72b6989 (commit)
via fd3ff10fe28782177dff77b15293bbb3306522b3 (commit)
via 91d77e0da5203fe0d925d297d448258f40f21099 (commit)
via 27033b59dcaf34216802401f19ec0b51e572b098 (commit)
via af1da46eef54e96be6765922f42a786b84db2014 (commit)
via b8fde1ab760f06e76c06e795b127f66784eb6aa7 (commit)
via 01461d51fa2a533092b135604b33747908891792 (commit)
via a6709a421381c61dae6208d3cc503ef14fbebd43 (commit)
via 807b5a13c25760f09f10bb8ea9242277b8928fdb (commit)
via c1ba429f5eb098d7b1a9498fe11b354bca8283bd (commit)
via 235a1870910da0a8b567bf47c5fc8467d4bcb102 (commit)
via a8326a6fcae7f3b0fb708323750e781221511ffc (commit)
via c9825a5714cebba054619150cc6dc74dee8f94f5 (commit)
via c842187ad91270660a91ac054953649d3f011d54 (commit)
via 86233a2049f66fc932915367dc7f26e499b0303c (commit)
via 62df1d46455dc5cef85adc07031a07fb3d0e4b6b (commit)
via 8743e25c800ac5994e11c932a2196fd8b485a737 (commit)
via c2d2a53b14e6330281b2c519e1bdd129d4de86b5 (commit)
via 9672fb595c581044a01cb4836d33afe6b58f5ab3 (commit)
via 572d3b1ae55ebbca06289bc53035042472a50138 (commit)
via 70f71c5413f70d9cf5716911101e48face70d080 (commit)
via a70fd03351709b036a8ee48cd8276b400661e741 (commit)
via 33d80f86895ac4ace68327554995503471054421 (commit)
via e2ff6e8c7eb46427bd96557566728cd64593449c (commit)
via ea84394f03c42756deddc0c575925dc4ee5b4e90 (commit)
via c4824042727cb37018bdc7fdc1986a909b6f0853 (commit)
via 334ffd13161af15de4bfb404be77e73e085de7f6 (commit)
via e5336c30e084c9adb8c3c7aed879ba6004442d71 (commit)
via f7470975b47ef0cff5fda310ae1ed17fa20df2ba (commit)
via 1f1d62190f05bc03a5d58b7cc247ce471bcf2b39 (commit)
via 84a3a650b35dcc7b69c40383d28795a678d71044 (commit)
via 9fecf9fce1733d3df9e6dce9545c1427c47265d0 (commit)
via 606ba69e360a2d841e9ec5eb7e5bd32d2c86f062 (commit)
via 14e57fb23c208b65884b5b6377836c15ed3d341c (commit)
via c5526a8ccd41201df0ec98f7b0da06113b9e6e12 (commit)
via 9ece3d02afe58dcc487e0ca0a4c464f1ba53eca7 (commit)
via 3666cb4d97d21903f1637796edab95bb466c213e (commit)
via 42c5261913f5277e51be25a4e3de08223cf8949a (commit)
via 15d0b77ba78bc2507b65072a74294a85ee7e5440 (commit)
via 0e2ef3c72360da9d0fcc4a36470bcb6b02591581 (commit)
via 822fa31524fc37af1ffdd265f8554fb9d85471f1 (commit)
via 2c50eecd72f4e59aaea65cab950ae058179ae1bd (commit)
via fe8a9e7c2c315207cf643f27115a78e8b2b4eb7f (commit)
via 550a5e0da25f7b3fa8051d988d70c116490b0f74 (commit)
via 286787bed2ffcf6ea6ff2da240edc9f78ef0660d (commit)
via 103c817670b1d4f839e8e82ae01fea0fe3aa1298 (commit)
via a709670c0cb9ca9f1b8ea4f13a7191fcba0ab8cd (commit)
via 2d8efd0e48d097a54753ea6504e8e69bf65f41ba (commit)
via e0eda94e0b5f31efae914bf00deedf0955590c62 (commit)
via 04447bd12e293df0f40f46e16c87a77f49cffe25 (commit)
via ae7e56576e33265085f6542787d11d28efa097b3 (commit)
via f4c12ef1784ce3abe462de0328446ab5c7702339 (commit)
via c405fba738036d7df5fe2b3792bb70a9b9eda9f6 (commit)
via d7441b27a527262614446fd678513b8d6a819214 (commit)
via 52d965040ccb6e919a34abd95240fee3718c0247 (commit)
via bc8aae68ec152b7dce35c158758537bd9912688f (commit)
via c328aed83b7d9506291283b6afbeabb4a5455b07 (commit)
via a69ebe7341bfbbc01037e71e66b0c6e7121ca6cd (commit)
via 92758ecffc245b5f9a86dbc8a2201a9c1397f7d8 (commit)
via de29c07129d41c96ee0d5eebdd30a1ea7fb9ac8a (commit)
via 8aa69ddf3d2d99fd9050a07c20eb01dd4287de95 (commit)
via eb65b11ca44fb2b2c128b317449272240ce337c3 (commit)
via 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246 (commit)
via 5c72873a1145a7bcdc70d8b1f9ddce1198548c3c (commit)
via 443cabc1e4a305f19b39bcbdb66c0bde010c93ae (commit)
via 3fda721ea7863c9e61728c6e2b7f499705ca9abf (commit)
via 041637fac582e9b2fd003b557e1d616ecbb0db9b (commit)
via 1ee4d989baee8e0fe013aa0c1e63f4e6a429d114 (commit)
via a56f7ff8c099d2d97a177cba073ee413ba3e9bd9 (commit)
via 6fe86386be0e7598633fe35999112c1a6e3b0370 (commit)
via 1649bf2b29e4770739fdc517c4e0d98253dfbe0d (commit)
via 3c779a4236347f9d6128de3fd7f0ef5433a304a9 (commit)
via b193ad87c312d2e6ceb54f0f2746425ba4f59d63 (commit)
via 9e655bc3d628a2ada61d9d8f7518d655b0f84e7f (commit)
via b1c2cce67f50c0da6ff45744f98142353166482f (commit)
via 320f960c727b5785a1869fc2e9898fea6b9e14fd (commit)
via cbb1e342dfef163cef8dba429aa8841093aff7af (commit)
via aae73017c63bcb2527bf36a3abb2c436072e3a98 (commit)
via a2b2bf47cbbe033774013a875e3197c37fd71cce (commit)
via 8792cbaca81edb2106863f8043fe490d148db54a (commit)
via 9c2193e32b9c07dfe1db532cb26de1c1e16d1b48 (commit)
via 6aa3a421c244e6caa57e42d9dd67fb2326ce51b5 (commit)
via 0fab5f49d919476e7c58024081f3b939be51ff15 (commit)
via 21da96d4bcb34a2289e5b16eead8ce181d8eb61c (commit)
via a40fac0707fac08e85b06974092bbdc15720f37f (commit)
via 196c4d239ece786aa29b86e6b48bef337ca330fc (commit)
via 45e97b85271f437e9f3897fc78e82cbac8f2368a (commit)
via 1f943baf9448dcec53f5bcde3e9d21fae2eeb121 (commit)
via 7985a4bd28b3fb77fc1245ff1e0fe39350a46db2 (commit)
via 4b79e1c508a01dbf6b1ae92f31e6256840b80207 (commit)
via 0462436914ec6bccff8e7825ba3f55d50acda03e (commit)
via e65b7b36f60f14b7abe083da411e6934cdfbae7a (commit)
via 33fb59eeb630ce1fb6055a6a8d26c46951fc9c78 (commit)
via 92e23241bb54197ec11ed79691bf678cd042de6d (commit)
via f54d04b85937244a7c9cb00dc388387c1819eaa6 (commit)
via 26f89e5cc91ec20cb7607e81b82ec7087d40de2f (commit)
via 351dbdab0925b72ddaad6f95b9008426ceb834ee (commit)
via cc557baebc622a96b3f0813bde163f0397964a57 (commit)
via e000302572bb2ea1e407218e6d5484cd4bc54f25 (commit)
via d81727d1263a242de4ce8155f7e7708a91b37058 (commit)
via 0a4faa07777189ed9c25211987a1a9b574015a95 (commit)
via 99b2935b21ba5ae3dd524ad5b781e70e4914f0e4 (commit)
via 92ac412b79d7b8c9d802530b9019f4c918d6fdd8 (commit)
via 7631aa8f7c4eeb998deabfdd60a9ee6eeb0d05dc (commit)
via 160677ea7d08e14bd2fc736f2d7b6763ceb2366a (commit)
via 3de3abb8876fd8037712c9b6b5f216a0d13e59ef (commit)
via 74a0abe5a6d10b28e4a3e360e87b129c232dea68 (commit)
via 27442da4487da8da452967438bb8c784d0ac05c5 (commit)
via fe03e032e35e80420588b047a208b76016c3f440 (commit)
via bd877566cf970b23df2d0a533b50c4e631e52788 (commit)
via 422a1fcd67e40f3e398c292b50952cc09bce2623 (commit)
via 044c4779138290c78c20018d7f083aad7604d823 (commit)
via 09ff2c9562e3d82418e9b7e4adad99eb597fb1c3 (commit)
via d1512ddd6307de6227bae7e32c5c72b5f781c834 (commit)
via 0585f306eca6145a94aa8af700bbf64f49207d15 (commit)
via 7a2d5ea87d43cd40343eaf920bc2086fadd2b7cb (commit)
via 50ca8b1b82973adbc44594812733aad0dd28bc2e (commit)
via c3d1b18082f595a950de2ca4b9b6692b18cd44a2 (commit)
via cdd87a91d1210ddbb24a2a1fb7bcc101dbd65329 (commit)
via 487503d57b7beeedc2649ac8137c2e44040ec105 (commit)
via cc9c51e9a2c5c3ce7a662d8efb7e9fd01ca1c1f5 (commit)
via e5baa7542ae9b36a1205703020ce8d2ba0e2e9d9 (commit)
via 1c0b26f0b66fb051d112e20cc8159d90b65722d4 (commit)
via a9eef903b04db257e42e05b1a296251d4ebc6c04 (commit)
via 7d7b75217f01f143f759b075005c5da834b172e6 (commit)
via 230ccceb4112042ce3aadc7cfd6c9780afa23bbb (commit)
via c0be62cb0f27a0855c007beabeb0c3ba3c4fb0ff (commit)
via 3f9c7bb8a987dc1b4e2a9266ac95cf231d8fe621 (commit)
via 0cf505f04be53e6f198cb0d091bd90d74e597cbe (commit)
via 669a650ec38562f5459a5144cf8549df1b3f1cc4 (commit)
via b73262553443c404f3211913272a2ff89be904da (commit)
via 8627b6eb8915407f8bf29786b70916b4b6ac0567 (commit)
via ea1a4ef9ac12b377a8c6b407ac488a1cb3805ba0 (commit)
via 172e704b668189d6057c4bfc80403edd59aadd5c (commit)
via 8ad7de967cfee9788fa269640f1ce9e0d302ee04 (commit)
via 3a5d06d3901871929d310ea0e968611e7bfa0f27 (commit)
via e5ae1675a59c6116c1d0c471b760e647bf6d31b5 (commit)
via 0ece883214d68bb606764dd8c6572487af976833 (commit)
via 00554310f20b5a29fbad6a6a9ba9856c5570853a (commit)
via de08d209347b6639bc28d1847344c80616a6cb87 (commit)
via 1b7273a397602ebd1d04d692f517941624d89839 (commit)
via c9bd919bd97e5fd2cf3f50470a29c119bd3b1151 (commit)
via eb5b276de3dc4c556c818ebfab63a14bf2f562eb (commit)
via 0a9dafbece0545af535494a7e5745a14c87ed9f5 (commit)
via 8065343bc1c50353260809254e1994642f2b1763 (commit)
via e089de85aec2c705d7fee61f1a51e37aa2510756 (commit)
via 398a65223150685c4d022dd980d0292e8116afb1 (commit)
via e76b79a1b16a388113c66083f5363b085814cdab (commit)
via 4f17a81c04b1364a0835e9a9b9c26adc6c159cd3 (commit)
via 7942b605f69740f853100f36abad05e9a98d1e90 (commit)
via 3b1f845a12b4b30ae1cc4c1bdf2af8923995dfca (commit)
via c129951474788781d54007380769ff3eed0f15cf (commit)
via f4f1c1f0006d665f2e2216e3872d82b5ac25334b (commit)
via 1572782282cd6b5835ad20f5d8538511616a7e2b (commit)
via f77ed1ab54bc60e08292d58fbc4cfc635cd1ef42 (commit)
via 9e8813efff273e390680ccfd1e8893c06a4b5461 (commit)
via e4e8de317591b5ac9fec818693fb2cdc5ce9e2a3 (commit)
via 03b92733bfd3c13bc86c8f878b274cf33740ef15 (commit)
via 8afd799648279d29c8fd9f2505383bf6ebbf0730 (commit)
via 807cc540e16eedd53d714a4d9b61a2c24ae31c30 (commit)
via d87597de14b3c81e481b51ea6de8b3273305487a (commit)
via 2a69f5cd6a1badf9fb1538cd8f1ca56cef054ba7 (commit)
via 7e30c020cd6c0babcf1d2c426ce72eb6a83a7fe4 (commit)
via 17fb43189b8e6a8defa8f6e0a01b13d8ded65d16 (commit)
via 084a16684541f0d19e99998a2a29a92619ee0b94 (commit)
via 8bf2c98d2e62d95c8f86d41895f4cfaca3371d68 (commit)
via 12dba9fb977f4ca3e62da1f0e29739ca5a534ea8 (commit)
via c4b7c85d1a314e59b20aa055d81cff553b3b8de3 (commit)
via 3e2d8b00ba86773f3cba03f935e5847cee109f84 (commit)
via 32011f9a367b479a983dbb32e877590b18fbc275 (commit)
via 02f306c854846cf972886c8f42e1b6e642c89e4a (commit)
via 6af3de2d2ee4b43bb5056d8b11cba164ac693346 (commit)
via 366f210b8217811fb1cd96ed996f3491c7d9766f (commit)
via 93eddf78d2db40f314323dc708252c87ed846a48 (commit)
via 835553eb309d100b062051f7ef18422d2e8e3ae4 (commit)
via a6b558c0d09f1812f347497857ab8aa42b33b0f7 (commit)
via 85c5f47252f0db064ece1a00c121cca9519d3b42 (commit)
via b59a6d92e4d44ba1556b8b11938a2ffc2e2fa7d4 (commit)
via cb4d9b014ea7d80efadfbdbfe2d1ab1dbde68bc1 (commit)
via 5c9dba4001ebbb432a7313a91cb3bc7b440bb396 (commit)
via 1ba16e4d72f4923946981997fed48eccb786a0e0 (commit)
via d7b4201a3b8daa0937883569b86085c42a8dbe1a (commit)
via 3b1307a0711edb6adb6e2e5532e302b5ce898537 (commit)
via 27597c6b64f541464113088eeac038b33d794096 (commit)
via 612e52448e93d3d5f0645117a0263428a4d5c7ae (commit)
via 1261088d77d3be53d8bc088abe4d4c5d9253dc40 (commit)
via d5137a32591ee4e832354607dea8627ebe1e089c (commit)
via 80de8b943cba0596032073ed443c573b341106af (commit)
via 2f505102370abac68e4b5dd52c39aecbfda41852 (commit)
via 63cce9fa516b2a36e13515013d45e95407ffce36 (commit)
via e6898f852cbff7dc11eecb74bac36d632628c639 (commit)
via ec661eb924e484b0591e155302b1cca5ccda3234 (commit)
via ab0db5a96545dece318f354fcb3317d1358060eb (commit)
via 2edd97ec54d5a3df9e11e5e74188558606e2e3bd (commit)
via b70d80fca2146f8adca9b5c36e0806911097d2d2 (commit)
via 3a2ccbf78e869817a411d735e28425713c220ffa (commit)
via 06eeaf025beb42ff26032d79b325718c97ed4550 (commit)
via cc62eeca9e859daf93603bc2421f3f2988870d1c (commit)
via 52a60eb5c950491c450ec933f66a56318b4da669 (commit)
via e5fb484fbabf454dc0fea14ed4564061555af275 (commit)
via 03911f1000649e0db11d1bbd956b2df5732a5a44 (commit)
via 331a48e08d6c155f68eda2218fc04ec7cc276cbb (commit)
via 22d72fe82da9313ecd73bae3efa61788f0b8c7dd (commit)
via 755a3b5fa194d08e883fec5899ac4b7bcdcbfd18 (commit)
via ce7db48d3ff5d5aad12b1da5e67ae60073cb2607 (commit)
via 1c59115d79bc9a80821a72cfab2c1bb6a80137f2 (commit)
via fca31329405278e34049e2e19982a82bc6fdf4f2 (commit)
via 512ccc6d8b7bc49b085289226407fe741ebe7a54 (commit)
via 126aeb9d11c7f1d670a30bb2a5a3cad31cbd9c0d (commit)
via c23f87e8ac3ea781b38d688f8f7b58539f85e35a (commit)
via 1378932f029f1e74cc606013778863161feca8e4 (commit)
via dfebd6128b4088ba489080cea7c53f141dce90ab (commit)
via f2a0e7ce76ed227a7067919a1f44cb33ccf1e826 (commit)
via e6d402802cf16fb0d3c183ea32658e4e7b6e278b (commit)
via 44f835de6b8c19c7b6ffe755d956d24da061559e (commit)
via 2f3467a4eaeea16afe3119ab6f6628ed1a7f941a (commit)
via 8e39d7eb39a7174ac39b93e5425b9fc8f35feecf (commit)
via 678ce7c7ba26b9728ae3a870896e8c880a618918 (commit)
via 303f3a44c2676bba5248d2e17b93823ac127c7c4 (commit)
via c366a4f8deb99014add34a7c79715beaeb1d63f3 (commit)
via 9ea3182d49d0ad15571ea962ae34b68bcf5a200f (commit)
via d9d6998c28cfcf825fb2845acc3b4eb705e991a4 (commit)
via 181226ac0ced78a1092c14d54764828637a4d814 (commit)
via 9430b1fef5b2cc99e97b4cfaf6ccd518bcd76198 (commit)
via 0d69a3ddefcc66ffcf05683763ef2c7f27b7c392 (commit)
via e19573d76a7f11827ee1f2dec889f3edcf6385b9 (commit)
via 261a171c3bd856a32cb40a280320af1d8179c78c (commit)
via c59714604459a820819a288ff03c4dadebda098b (commit)
via 4a9ea530abdc7aff1f58f6c92c281e5ed35d6741 (commit)
via 7e8e8bcaaba5f836b3868f6601e8b4e8314a431e (commit)
via 4e8325e1b309f1d388a3055ec1e1df98c377f383 (commit)
via cbf6d3808e596d35fc8c83a5f35e13dfb4c2449b (commit)
via e6ebe1de8e6ab2982869ba27f1719e63fddeb0e3 (commit)
via de66fdd73b8f545768166b7406be878c137cc019 (commit)
via cdbe6dedf437b052cb60777c470cfda312f50b43 (commit)
via 029ac581f439485d797d195914f52a60eb593a60 (commit)
via e8a23d31110a2824368bff0c41f79016d696f613 (commit)
via f7e301237c7806b582172fcf80a892659a3d9e32 (commit)
via 4f806512c8b2a99f3b1dc43fda7130ec81d21c1f (commit)
via 87cd15b3cc34738753d97e62a07681b245350f25 (commit)
via 3c17e9586c94a0e4331cafe79e379e71f3d8a561 (commit)
via bae03c0fbf0a82c2b88cb683fc81c30ab7f70ad0 (commit)
via 231bfc60a9327295a2431f7d04ed5a26962d6236 (commit)
via f1f6c26aa9f45367159229de64ca1545c87d51ac (commit)
via a6865a7686a02c7026150be1304ee682e8ddbb51 (commit)
via ea88e8907ab138e2fa1c5271c8a03ec53b4b08d0 (commit)
via 05fc975a3d825a10c8b9c8df509854f9f69d9c87 (commit)
via dd2d08d8cb0ed409fd8a423c988db7a0979e08eb (commit)
via 0589469f4a57ee95f16ea7e316365ae0641c4c3c (commit)
via c84efb2586d129061f0fc0e1bcf291e0582b0f9d (commit)
via bdeb3c67d12483d7ed6945e7d190195764e24fcc (commit)
via 65a83c79b14885d4a4106e1a707ef431b72053e4 (commit)
via 2fd85850524510bb6d4452f2c9b863400b3949dc (commit)
via 233aebcb70f15914e141fef4982ad7d0028fbd9f (commit)
via ec4581da1a15031128504cf97c8c578669422126 (commit)
via bedf7f9f268f67f5522e620a385452cf6083b4ff (commit)
via 70db0c50bf842765d90e2a6b3abf4f5360f31281 (commit)
via 609e5bf4005731160716d1d63d3eebf402a14f1d (commit)
via 71cf0a22ba08d32908a610b749e9aefe1c4dda1a (commit)
via 40d54f7e2b04dc53be81a24e50f8e66be968e6cb (commit)
via 30e529ac56321e0654bf8bbe48412b851bd06bb9 (commit)
via 9002ffc08e7c7fba799e8b417a1d80b77a20973e (commit)
via de92f31ea5fc5991e10bc61c175373b67f78deab (commit)
via 20ca327e9bcccb2e83ed437e7d1dfc9fa6d8fb3c (commit)
via ec4fcc79563b93bfc1e300c46287d8f102261d68 (commit)
via 8201bad569bfda39027198bd920e8da6b8af9539 (commit)
via 435309b12de7683b06fa29d0b2b9a0bb57fbd9ef (commit)
via 59861491526ae8b4f61f94e8ad4a6926178d9aea (commit)
via 9bdc1899a220d369cb7ca1b86fd9a2ddde44eeb9 (commit)
via c831a1e66e88bceb6a317dd8de0796b315681f4f (commit)
via 1780e7b66ee62456880ac5ed0d93c9830f233163 (commit)
via 238b343266ec6c5e7c610781021d235b7fcfb22d (commit)
via e0c2fad3471896467ad0ae3576f93b39bcb76a7d (commit)
via 501a5e337373d10c40c05d0838f5711fbb961ee2 (commit)
via dbf7313f6bb3fd3ace97b50509a4208864c4c4ea (commit)
via d506b61e5f2e50d228ac8541d6927594861a25e5 (commit)
via e66472386a716a31089a2306b9e5d51f7618feeb (commit)
via 90641997b368ea093686c97d839b2794b927180c (commit)
via 38db6aff7d759e5f271cf0d369459b361c8e60a7 (commit)
via 816eacf47e4168d336c2f64b1b621f13f8ca6cb4 (commit)
via 1d4751ed52516da9036c61a16946ec74ea1f1ba5 (commit)
via 09d70952ca89ef967f8652046ed6fe869625b42e (commit)
via 6944e7758d326b69482bcfd7d3f501df5113b394 (commit)
via 970ee33b88d3268780fc4a7d8950ba2a4a8202fd (commit)
via d6fb00a3198fe327cdbf4ebfe14f47eb23086325 (commit)
via f2a03c4538343e2a6941e5037925a63a7e71f68c (commit)
via 8bc5490b0e4718fcf5759a0b24c02a83c4518e17 (commit)
via 936137b0b10508b395cbd4a71ad07c1ec0ffb655 (commit)
via cacc927a7096327c8e19479058258cecec0eb27e (commit)
via 3da9d6198bdedd9317b290f70c9bbe613894037b (commit)
via 549e44164f1e4203bf1acb8d6c137f1de5a48f5a (commit)
via b1d0e42774ef51b404be44ee53cb930a630a63bb (commit)
via fd7f3a84f391957a09996ab869d079b6fe53a9fd (commit)
via fca9ec102de26345c4dafb193f85a0560322f295 (commit)
via ee0836a4c450db59764158e9f9c58acad7a1c4a4 (commit)
via 7c0a4abbb4cce4460c8b0cfd9f1447e0cc37644d (commit)
via 8f6903dc068a22c2966e9c28c90c14b6d83543cc (commit)
via 5ec0945ea8abd8ce3aa59aa851421269d9f807d4 (commit)
via 9b8747917cc300904783b403a64c2a11ebdb5ec0 (commit)
via 11ed5b5ca1fb48c7754c1d85b65fa00b00630d78 (commit)
via ad9807ad75f8b76413de4ab0478cff9611a2817a (commit)
via 1545ba5efdff605b12188442e34b5845c925256f (commit)
via e4036de3cb6229d38270225eff7a00f5c07a4573 (commit)
via 3b154a69231eb838886c82deba2b1bb9439ac3ad (commit)
via 11bbe308dff30dc66a6e72a1d357637757a53a77 (commit)
via aad4df7a662b0668592aaae63e66a49cd19ed614 (commit)
via 95a87b15ab32e307563eb8c4b5928a0919aeae56 (commit)
via 7b148d962b1d211eab660ac79e6dcb37e1aa8831 (commit)
via c0d55602e3304a95d7956b401ab1ce6c55d11dc1 (commit)
via 9f8aabb7951987f3e247ad4263e259d82211e5b3 (commit)
via 5819758217d6e95348c722bafe0f79c1e28ee43a (commit)
via 6edf83daa67ac95bb45c2975e0511a830e64ac59 (commit)
via 31dd54a026b5427979a932f0735bacf8723ec7cb (commit)
via 2c9904e1543d72dac392f1592580d9f8351f5e51 (commit)
via 8da8a38bcf022e5ad4d99a64be6cda1e557c5692 (commit)
via 5911431e8fcb1728b1a4765a6f3c1352c4879e8c (commit)
via 5f03da43d3b7cc5225b64f085320d501c8e1cfde (commit)
via 3ee8c9cdab87054662af5a67993c9a3ced593256 (commit)
via c0f25aade83aaf90bef0abd8bd7eff66cdaa7684 (commit)
via 3666f50858de194cbca5f0e3e8d9fba747bb3250 (commit)
via 2abaf1a0335fc470556c2506a9dc7ff002cca8d0 (commit)
via d5f52afdc5419756f514f79ecd53f59090555c43 (commit)
via 203b4a05936d027793b03316bffa333e539876ad (commit)
via 20ff1f4962b99620990c326c50cad7fcef0d16dc (commit)
via ec24cb1c279c83107c9058cbdbd9115d84d3ea71 (commit)
via 1a2787f32f2376d76367516f5cfb3a0059b76474 (commit)
via 7237139f1b8ec9248fa26e9ae2e6987bfb54f288 (commit)
via 2f554072bc7b82a18d898073802f545305a90110 (commit)
via 191d96a58702a5a3059993a1287136833ec94a4e (commit)
via b6c480a59485c8a53d9beea2dba85a667aef7556 (commit)
via daca34509f65af73d06ce071340668258539f6c6 (commit)
via 650e0b501ab48af61704efe9cb503490716c8eec (commit)
via 45c04beca6f7d07f1a2c779baf3a7a8ba26763a2 (commit)
via 0b06fc6c1993467408bdb7f109affe029a834e3d (commit)
via bc44ee8bf3c35cacaad05751d75e8789aa5c9108 (commit)
via b561ee0919676f6e9c34329151c2b46d60f1824b (commit)
via 372f3a19f5bad9ed51e50d083063fccbe1ff6900 (commit)
via 495d4e2f6970ef05f5f764f1fcd6661b5403cefb (commit)
via f634e891b21b4533c16871bab7bcd65f6422986d (commit)
via a0662ae5601ee75f3e99b2b53d4f3376b5e6a037 (commit)
via 0f6b84e5523df12f5017ef7615bd0bb25359cc5f (commit)
via 10e87104e0b7977804c8c55275a403602dcbb2e5 (commit)
via 42b0cb7bc7423e9f78199ea2f60a449dd98a2843 (commit)
via 6b59d1bc4fe7976ea6c1cf7820649824b4135e07 (commit)
via 2894c45545298103feef9dd0a5f06405d88d67ea (commit)
via 24c515276e5a15558824fa12b8fefc9b80c315d3 (commit)
via cbffb4ac5bf27888553a102ac6ee2a1fbf07642b (commit)
via 5c50504446f431198d329d31b62e076d65e3eacb (commit)
via 403af3331c71af8cda87fd6ff5fcce407d4f935a (commit)
via b548f4ed6cc20a5d8bb802aed646af007c0653b8 (commit)
via c90033f1d91fd82c7bafbe645de95281c46ff46f (commit)
via 7e5b7f352db7ef0778b85dbf73be66210b739b46 (commit)
via 92226343e053027d4abc7a9f2406016d6adfc112 (commit)
via 57fda3f1db7ebbc966ae1ab980409a923196ba5e (commit)
via 594916b524ff40cd66ce7891ce743c70a9d0aa68 (commit)
via 59903362374b1fca9612fe5a872579dd4a4d9873 (commit)
via 8545674c14771700ce0aa611f3fa734082d6e444 (commit)
via 3814969790ec02dceaee88372a977a52bc210199 (commit)
via e4ecc70f8d016e19fb3274a8d2e802cb22b76231 (commit)
via 3a81d2d941bb7f8be0219cc2ec4176c894634237 (commit)
via 425e18f7045e7300230bb2876d69a1ec9837a6bd (commit)
via 3e5f22520515b1f1675c3217ff686106201fdfdf (commit)
via 947a2d7aec3208efe49e266271860982d355c0d8 (commit)
via 292db149be50cc3a38965be1e14ca1cf26cba2bb (commit)
via 5bb4db417ecd1b94170fa87d11d91d604616c1bb (commit)
via 7cf0d504794bbf42d15015af1d42ba1cf7fcd82f (commit)
via ad7b96467ceeeade2ca9bc16ba8f02ecadc1ed82 (commit)
via a5330334a54791088394fe6b726666b9ae6671b9 (commit)
via 9c4e97e7f8cfe7f8001c1491f9f7c2e632c736b2 (commit)
via 5d239b6aed71f987330add0bbcfc7baed670e804 (commit)
via 9440d65d7e71ec30e0d268f5f9f5b1980d288b98 (commit)
via dfd0dd105b4fc8054e3b9cfd1bc5547302f54578 (commit)
via e441d6b05a022f3c0a7140ae43fe42d5fd12cce3 (commit)
via 0ad163bb379066067551f1f3623afe43cf5bd327 (commit)
via f7f3060b97886bea4fbc4408a5c432e81096e3a9 (commit)
via b9f1eefe7e7040db4841e75a38b6a9cd1511eb59 (commit)
via 03dc2386b14131c0eb35408952e3ddc8ca017831 (commit)
via 3ae13a89a2f238602d5dd7e3dc12da963de21ec8 (commit)
via f73f27474fe73abacded88f4dad74867331cb402 (commit)
via ca8fc9f4147a8f332280fde6cfad99b963ebc40a (commit)
via b48ecd894a0ee52c132e041f686938bdb230d743 (commit)
via 638a2d7fbb2a0548e002d57f662f51621175a3a4 (commit)
via cb52403a8ad934f8dc32c6404b5c97b65057d63d (commit)
via a9a6007e022bdc61bcec8d01b51562f9f33f8fd3 (commit)
via 3e964a48bd208e8a7dc04ef1ff8de5cf1da5a514 (commit)
via 8ab540a511481e83c8db435c439e291d0f3e52ea (commit)
via 1dc0f0871d7045e8fb57bb0767fc31a2f7e3fe80 (commit)
via 1bbe13152cea9e759bb0423750f244be58eff578 (commit)
via 8a21830a250996c93b1b053c56a4dc9bb7bb4750 (commit)
via ef364ccb84c6b8eea4952c0a59c6bb560ff43ca4 (commit)
via 45b4e930c9fa6e5eb6f8ef7c0c9cdd6818e82f18 (commit)
via 9e98b4768d26c1ea8accf3f53f9c019ff66347b6 (commit)
via 43292d60d91cd8656856c88dfefce186412b64b1 (commit)
via ac111116c588c5b611e1363d4fe11c7ee8f4e495 (commit)
via e3afa9c5286228971db62ff868785602e90a5513 (commit)
via bbefd6bc4137c3345845d09e86be68b81770718f (commit)
via 8e32eb96aad5d079931b5c7afac55a02f332f3cf (commit)
via 6226330df851a092d9df353081fe1d9c10743d36 (commit)
via c73280d2ebe15512cd4f09ddbb28c2f5030cd86f (commit)
via 1c4683ddcc108f12b355c5e7b5476671fa6c8a33 (commit)
via 25f65134a9058c888a4181ba413ec1756e6b1d45 (commit)
via 5c8cd4010e950d6d0aa68586252c5a3039e5a248 (commit)
via 3563065b6cc893ab8436973c41eb388dbbecc07e (commit)
via 41754f406f6edb8d77bd84b34c52c4a2487143a8 (commit)
via 9a5e89e0e6aab0e50005cf9f9d7bf577eedd1bf3 (commit)
via 5dacdd2da8710b91b85fd73d025d38e69317d6dc (commit)
via 6744c100953f6def5500bcb4bfc330b9ffba0f5f (commit)
via 5e0e2e53c5c5876ae540a0ab26934c0e7242a673 (commit)
via 2b8518d98293f746baab407d7d6744bde367ced3 (commit)
via 022d3888199e59b7a4bc256eaaf188a64db61cdf (commit)
via d4b9c33282199c912e6f795f6bbfddde12510795 (commit)
via d27729277c9e4c9bb4cf121700d67181d6c0aadc (commit)
via 09cd4b9b32415144a2542171558b56226322bd7e (commit)
via 84e441c75606aff87131e7d84010679a11f242ce (commit)
via 597ed99f869f9d170c93467e8abcc2b03959f75f (commit)
via ae3a1e5d17d5c8ab8c9dcf20cf60a68744f5cca0 (commit)
via d52186367e1f8b49dcb5a3b4825841a95251412f (commit)
via f70d131fd20085a472de32f86299a77647f18fd5 (commit)
via b991213a389213fe42ef0c87bde56e462c025229 (commit)
via c6c7cdee4088d6be3728ffaf07a25b920b0e8b5c (commit)
via ebea1d79bcd55536f89e250ce1be13a8369171f7 (commit)
via 610720a5bef22ba7193c9ad6e3cbe318eebdbf14 (commit)
via 3b408810eb76ca8e360da549b0e271d15d02b0ce (commit)
via 159af7d5de17a74269cd4af69ac3b569495c4a4e (commit)
via 54663a3c7bda67a6813d2478ff44c8a07d9c48b8 (commit)
via d8748ae0321eca340142c98f708cdccc2d62bda7 (commit)
via 00eee13bde95c741890ad3ca52967c11076c5087 (commit)
via 22a33dfddc0b29a506b57c3f76bace8d15ec6df6 (commit)
via 7793f98f619c3422ca3fa26795a5861b0fbab4fa (commit)
via 9f6b45ee210a253dca608848a58c824ff5e0d234 (commit)
via fa4d217e1e5977170eda350f3101616d0182a21b (commit)
via b89f0e0e69cedd0018a5c558bac1db6df944ba3c (commit)
via a5683591b24d1eaf659b3820707f4de08bb59e40 (commit)
via aac7b173828dde1f9bfb1807828132b15c58674a (commit)
via c806c0c3184caf046e124f36028122a965078408 (commit)
via 28d885b457dda970d9aecc5de018ec1120143a10 (commit)
via 11e658493d1672d39194580ffb1dde98101608a9 (commit)
via d3c5e8d90a97ce403e2e0e079094d20b43fc8030 (commit)
via f1035ba18d68d4a1fa89719a9dc8e5eb6ae24d84 (commit)
via 0740a5d8df7f65518b2b87662b4375630e1838ef (commit)
via 045d32e4a9f14e7deec58e5e7d55867a34325764 (commit)
via eba06eeedde4091fed26761f696a2aab5535074a (commit)
via 93d27cba1a47182b5f94e45b5d569386f2a1ad25 (commit)
via 8a966b8d7359a852478e380e89e94cfe2f12d5bd (commit)
via 2c2459fd09fd1fb9b2c741eca0b93a8550f3f9b3 (commit)
via 145a4ed5a89c92d93303604ceff83ec3d17ff87d (commit)
via 41ccdad87bfc96af06dd83ecd143e4277795eb5f (commit)
via 8682cdcb920f12df639aaa0e7720afb9b03bf849 (commit)
via 0688fdb85ded658b2ecf5aceb6e4691cb921ddfb (commit)
via 0854840b717e6f79ba5648571dd0c71306af73e5 (commit)
via c8194121792a6f19615f76de611cfba3af927a07 (commit)
via 1c36cb6a3edd1029e115862fbede8a81a6a96cf3 (commit)
via eb79abb35f62e96268eb687c100c1f1129d15537 (commit)
via 0c36cd61ca034017db27d2f03d7a791c4717813a (commit)
via 41b26fc471f6e15525a7fe5d522529eea45c73db (commit)
via 7024914e4253ce983f0dc28f623956ea6c82241e (commit)
via 2c10fa4964550d7402ee0fc93401f8ad39f29e87 (commit)
via c3e696cb4ba09b3cd53842d684f98262cf0cee7a (commit)
via d85d48eee9608db9f4c912e5cfe3ae1f2f726c05 (commit)
via ee7dbbde6311ec0e9519169871aa182dd7cc1440 (commit)
via e8dd780ac88735b31bf3e7df6dd9f4a2b972da94 (commit)
via 43b0ea4216ce8578e630be460fa5eadacb712c40 (commit)
via e206bfd604fd82c5747bc85e3f1e8d6de8b14908 (commit)
via 31b4c8a77f74ff4f417171af1ceb428c134572a3 (commit)
via 0c58aace22046e4e58360df79299150b745994a4 (commit)
via 69950d299b32dea11452b629fedff973108bc263 (commit)
via 863da09e4d4ef50693860dd95948ae5171ba5116 (commit)
via 8b10f38aeb02dba5b454c94e8788de450a5955be (commit)
via bb38dbf06510c2a9dfa60b68e56836404d62f694 (commit)
via c42c6b938afa4e77d8b0545f1b2dbd335c9d3890 (commit)
via 019d55a3b50fffc8694f982ccc457dc1c61a04a2 (commit)
via ff2167388ff8a86dfdae23fbd147602b102b0319 (commit)
via 01c67a64dc682abada0cd52505e2cf17a5029494 (commit)
via 2dd98a113773481b77ca0600c1d1028e2b5fdd72 (commit)
via 7cbe475737d43aaf4757b5df76d9bea75b0bf5a1 (commit)
via e4670244659f82b8fabdcaddce43d229a79f34f8 (commit)
via 4dbb7316aabd51e731a474eef597132aa0ae5863 (commit)
via 47e1dfee342b4b1fc269109d130bea5eeeff743f (commit)
via 2972b769eaa87705120211afd0cd4c4a5984c1c4 (commit)
via 522f3f2bd6d1b6a42e3052a1cfe7a1b0212ca118 (commit)
via 81231871c46b9dd9a06db6bd239688bd60270dec (commit)
via deaf3af3f2b12e34dee77b31ff28748cbaef3778 (commit)
via f8f96f9e0a1b3627f813cabe1b8034d844395949 (commit)
via af81d772421d616ff4d219a0feb371a9e2ec3edf (commit)
via 276a08d3371eef268c0e0b738d0c8583c3d8641d (commit)
via 01ada2f4e0e614d6e5378d277cc1286ed3715ce5 (commit)
via cb3ad87dd3ea63691991c091271697ab48ee4ce1 (commit)
via a1f129606c6dc70e512e86a12cf6bdef8b0d2dd5 (commit)
via 4a1003b53e1a2e5ae8291bf74e2cdb74af36f9ea (commit)
via 8e6846495d72600013021d331c8138943f1d4c14 (commit)
via 0a5970240b9e8ae08e4a06b67a0678a19b5def45 (commit)
via c12d1b3b6e90a0918e498282b2374ac294258cdf (commit)
via 0811abc7c8bc7ad8eb4aa4edb565724dcb4352bb (commit)
via f53e65cdceeb8e6da4723730e4ed0a17e4646579 (commit)
via a6c3266fe1acdfa5393f1cd2aa2a502615800943 (commit)
via 93f7ca576d5f9f906733775ca9173231de175290 (commit)
via c02e37894d858a0c3f70deb90ffba14c0ae58b54 (commit)
via 6921ae3d95c08a6f00f38dae2fb7f14622f49440 (commit)
via 331d377c8fc5f5a1876cbe52ca714b2a002bb103 (commit)
via e980e67482b6b82fab84cd4d2b0aa15f07c23f79 (commit)
via 0bfb997069b6574b985291a0a50f02487c3614dd (commit)
via 1b6614825a75916836c64bbf7a52d7913b189910 (commit)
via 1051eb47c63f0260729de5213cc4f95827edbd3e (commit)
via 79968a268be53f0804141068ddbb433dc5ff1bbd (commit)
via 27a137f19cfa2acab068d325dbb10d71f5864241 (commit)
via fc8bbf3d438e8154e7c2bdd322145a7f7854dc6a (commit)
via f47316a1d2b49791b785cf7b8b5e11a13a7a0fbe (commit)
via 8f0e9452fd3cc94ef44352839e91e2a4a1d91c55 (commit)
via 46cfa0779257292e55c048a3854f0df4fe1cbd08 (commit)
via bfc050ed6c9daf2537b5d7aad8fb6f4c6327e6e5 (commit)
via 7c73dd5e84c12f286970ae22c31aa4513a495646 (commit)
via f3da5ed9ef5a25ba65096620b1e7a7e21e04a2c2 (commit)
via f957fa651d7cb9b7e8364a58a7c438c367abb659 (commit)
via 08d0d10dd79cfa9b4f5b4d42bbfef15846d77d68 (commit)
via 56cfd6612fcaeae9acec4a94e1e5f1a88142c44d (commit)
via ca25d3c2eba98609d20cd646da422d6297e4f818 (commit)
via d561219a0e93d046cdb63166fa775dc20c563d9e (commit)
via a8cc78f205ee37f2c26a4bca3e82c6f74c22b9cb (commit)
via 877c06b9535bded5ad197086df8383fdc0bd8101 (commit)
via c6ceedc0a1137cf4f9db6d11d63f8e825d99b68e (commit)
via 9904f524d6a3d1f83bfa0cf94751b323f64bc233 (commit)
via b7a45ac8c122152452dcfce2a57df332748075ca (commit)
via dfb173cea580de804e40b93fc3e1d5cfcb2d9b6f (commit)
via d337cce4951bc4585f4109823b64b64346d0d232 (commit)
via c5eee92f9b673b5a88e902d8ae65c19c886b3400 (commit)
via 69e9734996d5d1edf533929598093cba0432e406 (commit)
via 00256ac162e5d142268d09d20c569109c2197b22 (commit)
via 0d0601799006a859be0e7a94c0fb3a93780be36b (commit)
via d25ef0869fccbb9d07644c80d369a31376dba6d0 (commit)
via 4263ba5f49dad8f1d282de20b9dc7644953472b7 (commit)
via f31f6dd3b5abf5e25c3416ac963d8de8db43175f (commit)
via cfdfb58ce442816cce069453d4051cc008691b74 (commit)
via 4140b1a97f5b436c77cd84dd1595b3d4273771af (commit)
via 5b1850bbc20932642495b8ae8e5f14ae5cd9c1dc (commit)
via 65962eeed622a6fc92b2e47ac881fbe7b54d3bf2 (commit)
via 553dfb31fc1ef98ae43e9d2929ba748c4ea14ca4 (commit)
via 3b2c2c14fb9f99e1f189c70441d23063c7a26b6e (commit)
via 0ce073c9aab78d0136e7c2ba48731741b886ab4b (commit)
via a08894f45157cd19450927d2e8b80ce49a86acaf (commit)
via 7e6602a873c952f8c01e141b4a40f4f2639758cd (commit)
via 3eab2458f1e6b122161361a50bff80e4f5d24825 (commit)
via 59c65b476bbc17417361b99234a7001a894b71e0 (commit)
via 139f66086e6cd02bfbf3b9fd0f55761ae4df3aac (commit)
via aac0dd304b86ee38049480c8155f5eb97142c435 (commit)
via ebe0d8a602ba6157bf519f1f79759a5bfcf1a4a1 (commit)
via b3bcbf0497d515f6705c930696a9a7cc7f55fbf9 (commit)
via 5934fc7661d5970427abf52d7f6538ac5b697842 (commit)
via 2f15c7833baf7c67d20df974093db92acb0ae09c (commit)
via 644d7aaedf08e25fd7f335956cdb65f0d8d5304a (commit)
via 5069933d08928eb2933c68ce08397fa2cfdb9e70 (commit)
via 6b846ec5edfc4833503d266a72b8d7c2ed7b29ce (commit)
via e27a2922bca6a9c9f53e867c187a6e589bf3175c (commit)
via 907061f81ba62cc5f62b55e7ea2b4e28035db6a0 (commit)
via 0c0888ef6aeb481f70d424766d7500f869899027 (commit)
via 70a7eb4ae1843148a27c6ed054f15293f8b9e848 (commit)
via af71689ee49cd8668dd830e7a90f06c9b7fc7de7 (commit)
via 56bcca870b41742f47160cae5b30b81ef62c1095 (commit)
via 9f454868d6240a3113bed21cd719293ddb10991f (commit)
via c84d8a4bcf3058d49c88962744102cb3efa7aa2c (commit)
via 9f48afa5e955391ea42332f2b6cc0d8f4757b438 (commit)
via 8018033c5e0e31be028a3c08ec33234d794aa708 (commit)
via 5378e305cb5c51dd3245c8844fb76a99e0f7999d (commit)
via b4dd93598f4e795c6cac151bc26caa56975fa835 (commit)
via 1eba6a3eb2b7277635af75688c0d56874d0a5f4f (commit)
via 66ef737b647294c32d631236f8879a733963ae08 (commit)
via d95162bee2f3bc75810dfdf2e2f1938690c5ceb7 (commit)
via 1b8d478d7b5ed39f1c64d6c4973addc976aa01d3 (commit)
via 61071513480693ec4d32a3aa73cd60bf63bbb808 (commit)
via 86c78e9e9cf17caacb76918795849b4ea9941d41 (commit)
via dc964cd262f9fb09d7e83b71bf5c5fbb0cb13578 (commit)
via 9e4d4dc41860e8c370c10695ffaa5dc7f30f059c (commit)
via ac0d3badda51a5bb268e92ee3d0f61a5ce519f12 (commit)
via 35c3fb162500afbfaab41533d160629a3f48017f (commit)
via 08a5a3d31220c660530bd69cccc8c27e1a789312 (commit)
via d9c0f5f2d9294d8eb026bf92fd8fda103e9fc747 (commit)
via f15be5f7bf0fa3e5381afe4bb820a06af434a5af (commit)
via 01ee67bc74edb15536ae92b558eddd496da13fce (commit)
via d135a0e1f84ed2700d283ce20c7d96e5be614619 (commit)
via 7f6ecb5e40c6acede82fb08a7d4c477030a48b99 (commit)
via 3d36fed3067cec991af53e2588220c7259021ce5 (commit)
via a9ed9aeccda0cb876e09d6a1bb86e4405fc970d0 (commit)
via 82bf3de5bba1230234b00b1c6283da0653bd90ce (commit)
via 23b2fe710bb60d7337a67cdaea2fc2d150586f9b (commit)
via f6c776bc4a3cea0eae628d1580ab10843be06dfe (commit)
via bc4926dac23a819a909992abf58df64d62647c68 (commit)
via fa3e24bb027671b5c53a5fa1b004920cf8252f0f (commit)
via a11abf30b29eda75f8680c8287b57db1abe612e8 (commit)
via b7bce1d66baed1e01a951a6b13151783b2d74444 (commit)
via ce7728b0d2d217cb0299b1a5b54470ed8c13148d (commit)
via 3792c2bf3c21f2b0921632456c362c4bf39ddd09 (commit)
via f9a8f7668e65dc98e7fa7699ce906efe78f4daf8 (commit)
via 20897c866a9c03bdd372f13c6a2829926b6c6782 (commit)
via 99aed51b37494e262cccb3dc34d4b62a6969b48a (commit)
via 12aac746522dd1dda1d9082a0c7bf67c6b32f455 (commit)
via be89b811e83d3729ff7fb48ed9d0e16348532978 (commit)
via 83053b85d90b62e575aa4e8c185e80f3165b0f8b (commit)
via 6131e5b395558dd51a63deb4565e2b9bc253c704 (commit)
via bc474e23988f7a815ac5de332498d8210a11a16b (commit)
via 0287ca83ae439396e9ea478e9e4bfe2ee78308bb (commit)
via 33a3143ca00c21437081918f38da6d3c2370330b (commit)
via a48d74bc879ef62d112fb017698466e65dc02e72 (commit)
via 200ed42600985f09ef96db73cbfa79a03e61a71a (commit)
via 9d2bab2003be39da1ebf2b96ccbbb0af45e42a1e (commit)
via 1c14515cc61d0d80942687211f8c418ecb3f6a3e (commit)
via 5a3cd7b57c0ad49e48d0cf3ce383a1d972deaab6 (commit)
via b6203bf733422707388f45bc0bece9b3fe40e49d (commit)
via 996cf7be03981de06074a9cd6632c59936d01bad (commit)
via a17ef1fb8c6c0d7f58060d9ea1699f54bef1d79a (commit)
via 67a672223f008112e401dc0d19fac5932d202a8d (commit)
via a420052d0814102e518d1e737046f7949ea8b378 (commit)
via fab53a075c7817cf2e8bfe06e76851d81c3e0d5c (commit)
via 648f693c9e524c49b0ad1e53096664fb5d162f4a (commit)
via 504f64b2975180c69805756c4e6997c9dfdf140c (commit)
via b24fd3ded0a466be9eac27617952728e44149695 (commit)
via 377f14a32c7f86653f7c0e1f1217387fda38d6d2 (commit)
via 843eaa0b842b335139c75f96275edc467bf6d7e6 (commit)
via c3d39b62623067beab9d99c091f0aeba10c3f833 (commit)
via 7d0cf9b4268bd037716a980db099ebc2e6c2da36 (commit)
via 996d6ff3b0e79c723332d03296cbbe88b552d373 (commit)
via 924c7a481efc5aa34c8bc73efb395c2c80282cc0 (commit)
via 8cb9e9efb9cd47301e6784840142931631e1664f (commit)
via f117a66b8be41e88a18a27bd7b2f1275b624767a (commit)
via 77c012c47a2bd2ff161b8b62df065b3daa87ff73 (commit)
via d51421238cd7aa392c6baa967ee977a6d3dc8b7a (commit)
via a9ebc379b9d2f2501c8a4d497bb715f6c100cea8 (commit)
via 1ab0c5acf4fa6b8a1a99068685bb2a27d6dd3dc8 (commit)
via 15249e567c9cfae135c1a2bc1a110b4a635b11ab (commit)
via da089cd9b9dc3aa3cb13f1c02e362b870e524b20 (commit)
via d848160aede1dceacc9a2e45eb4ebb00fe51f102 (commit)
via 94b80d73b69ecbd781d449798a8e993bcd2f098a (commit)
via 715fbf7729c4ac3edd50182978e3341e94a585ff (commit)
via 04e4c63889638247dbc4ccf31ba5be3976748a94 (commit)
via 20288af37908427592b5ff2cddb7d3551aee31d0 (commit)
via e77b710633d5cf1d34dea5153a0f77b7d1f111b5 (commit)
via b4b3814079cf33af52d9ab257464ef0685894b82 (commit)
via 5450b258dbeaaf876fd35c52a1191e4f083a620e (commit)
via b1c8e1ce93d760fa32e7b977910faa9ed5fc4c30 (commit)
via 58f0aac3a8f36f335c0546f44122ff43f42f5761 (commit)
via 84fbdaeddb176af0a5cd73caf76698208b4e94a7 (commit)
via 09f21f49fa1e214af117262c305efb3968becd8f (commit)
via 2241bdcfffcc27aea35b79926eb2769fd479f1e9 (commit)
via 2066e02cfc6950bf8cf8e40a9a678ad74563bd36 (commit)
via 35491a7585e0e7ed13ea9012ff03415ffb85103a (commit)
via ac107fab886b1adb0085ade12b16d27caa0e6cb5 (commit)
via 36d2b848c6badf00bae61bd958b891b7e2acc8cd (commit)
via 4f52e898a7b69228cf4a8ab70d78844ad9dc45e1 (commit)
via c2c64eae1f3e6140bbe30e963797b9a4efa5a9f8 (commit)
via 230de14b1b3e31b2f436cfd370e149a98926522e (commit)
via 54a707d39ae1943731ed2043f8d44424c434f0e3 (commit)
via 51bd304db036e8d2e6008a98b2dae01eea126e41 (commit)
via da1b2d7dc0a367ed168b366df1ab5c5af82260d4 (commit)
via d25a434d8e43ce6edd20be13fa2e0653fc23591d (commit)
via 1ed3ca78fe3449303aa2f8bd78e358fc52d0584b (commit)
via 1726141cbf07e20701c8e82e93bec507228de49a (commit)
via 31b507f443ec26b5891a2c87daf1754ecc289bdf (commit)
via 8aa3316316582b761d39b44429941679a3da8b9b (commit)
via b5d6b400ec3273ebdc3d78a9917fb1a38f4ca795 (commit)
via 5955402e8ad37224873938aba01815e0c4f53cd6 (commit)
via c7767cfac3978a900750b0d95294050368434f65 (commit)
via 587f975a2a6132667ecc7f6478534d7c4cf91459 (commit)
via 9397bd5dbf2b82a62f2006775ea92bb3b05b755d (commit)
via 7eb7d5efa51485ec96434c08249856cc8cf7c8c9 (commit)
via 38f650c71af54bc238221b540dd2fa302a862885 (commit)
via f7e29b88367c65f57c6a84383fd0ebc520dc26d6 (commit)
via 798e61add913f36ebf6967d62e456ec6c6e1b757 (commit)
via cb20cd84c91a2de735a0e31653cf8704efd5e159 (commit)
via 56b5c356dd0e122da66a0ee94c33368ff1f86386 (commit)
via d805ae680fc4fc5920c5bc50e2ae0817379df1cf (commit)
via 371ad74bd7276f2c5e6c9917f154ebd333a835e0 (commit)
via bedd068396a44ac7fed5a7d4c3f3a8a94861c327 (commit)
via 4fb2c320d91021da1a7fa3c3f9f2f3a2906ee24f (commit)
via 002527091f067467ab0d85837d12b89295e961d1 (commit)
via 1558c3b17a0c31adb4aa62ad2f9d1cff65056bbd (commit)
via 89a677920d39e4da45f49811f6bb61e3e46fcd49 (commit)
via d5c0c97ef8bf7d4b9ae2709b8a92eb1532f4c9b9 (commit)
via a7b31b7b074db8359b5c93c3b0d37c807a78ad7e (commit)
via 7503458897ccad85875ef3bc929ec803750179e8 (commit)
via 37da25f2eca6b2af80cc5bc2a7e65c5644acf2fb (commit)
via 0f228f2d53a626d647aa728585342c889ff8f846 (commit)
via 8b31b93c4c62b76b3ec70d0e9c1a18c0f51bd459 (commit)
via f9445ffa2d128ab0730abb3592e75475023ba755 (commit)
via 494bbca16cea584ba6ad5d2c5f9a273d809f902d (commit)
via 67f4798a8a2457b8d284b75dcf1a6bcfeb8a0fa5 (commit)
via 32e88854b4ab9b2484d1f5ad0597869c8d1f5da4 (commit)
via 9d23c50d12b04b51c10889fe9b476823550bd733 (commit)
via 83d4f414a296dfc757a7a4f6e8c8fcf7bd24ca2e (commit)
via 424b8d83f6059738f2edb01564ec9ce1db223b08 (commit)
via 3590bad22f52396c91663cc75b8548f6df134c60 (commit)
via 4a9b794019090fd826688bca0aee4dc565756cc1 (commit)
via 180a437742219f6266934738962663c65ab748f6 (commit)
via 6210dae2a6749d8c1e328de433c91058fd54b756 (commit)
via 6b8c62acd872220aeb061f7c6e0fe50348ba3d60 (commit)
via b0db66ca291884dd9220cda078d9ad3e91d0eb67 (commit)
via 71739287e490437d2f1278e02ba9232afa925a48 (commit)
via 7ba306a2e14dd31e1bf8350c2d27b9741af7e101 (commit)
via 9b6c74f8e85b29db87fb4724698d9fb9cc53dcc4 (commit)
via 356a8b2e2cdbd2470eb134bfe08f80fb54028abb (commit)
via d0af0ecaadd8950008ef65ee2b40b56fa21a1e82 (commit)
via 142825f0caeba214a4d1884be4067c62d1cc3551 (commit)
via 4b59798e65bfc49e153a4fd96fcc684c1fbe91b6 (commit)
via d2c2edbfa7d78ebeae4c79eef532814ac7072556 (commit)
via 552657fff4e4538628e7d2b98c4fbe93a07d307c (commit)
via 650a6c1b4f315e6e89de673502d5a82359f424d8 (commit)
This update added new revisions after undoing existing revisions. That is
to say, the old revision is not a strict subset of the new revision. This
situation occurs when you --force push a change and generate a repository
containing something like this:
* -- * -- B -- O -- O -- O (514cd120aa0ac15c4e2118255d6176e90add211b)
\
N -- N -- N (79a11d19f140d1420bc9d83dc1fbdcf907319418)
When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.
Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.
- Log -----------------------------------------------------------------
commit 79a11d19f140d1420bc9d83dc1fbdcf907319418
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jan 18 15:31:29 2013 +0900
[2225_xfrout] s/dump_statistics/get_statistics/
Due to the the method name changed
commit 7390503b44baf97dc67ddaf800cd82b28f097d65
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jan 18 15:30:35 2013 +0900
[2225_xfrout] s/clear_counters/clear_all/
Due to the the method name changed
commit 5072f7f444a75c7152ed74135629748b4f71f584
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Jan 18 15:22:44 2013 +0900
[2225_xfrout] s/counter/counters/
Due to the class name changed
commit eeaced2980d16428a1c523e1a0c77d7c7bd0ecb0
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 15:00:13 2012 +0900
[2225_xfrout] remove assignment of an unused variable
commit f99dea73ed9b124b72000b57f8ba8856f5838e9b
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:57:58 2012 +0900
[2225_xfrout] update creating the counter object and invoking the getter method according to the new interface
commit 620caae79d9fd369f3872c82540a8ed80066bf12
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:46:28 2012 +0900
[2225_xfrout] update creating the counter object and invoking the incrementer method according to the new interface
commit 8013bd184be4669ec53274d1b174bc119a5a73b3
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:42:50 2012 +0900
[2225_xfrout] remove obsolete creation of a counter object
commit 7fbc8323cd986bda0e74941d76af727fb5b1df00
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:40:52 2012 +0900
[2225_xfrout] fix the wrong counter numbers
It is realized that the removed dummy counter class didn't increase
the counters properly.
commit f8220e01112ddc39844db6bc69b5b66113f83641
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:39:51 2012 +0900
[2225_xfrout] update the counter object and its getter method according to the new interface
It raises DataNotFoundError instead of zero-count on the new interface when the
getter method is invoked if its counter hasn't been incremented yet
commit 8b9c751d904bfd58f8be4d56e7c168c421794fc2
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:35:31 2012 +0900
[2225_xfrout] update the counter object and its getter method since the interface of the counter class was changed
commit c98f15811814caf7bdf407864b88253f7311ecde
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:13:04 2012 +0900
[2225_xfrout] remove an unnecessary dummy counter class
commit f90889b6559cb2ba95a33108df1388afb5a12e7f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:11:22 2012 +0900
[2225_xfrout] remove an unnecessary keyword argument
commit 6392c36a0df416bf4476e2d5d3427105cbea98d7
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Dec 6 14:10:28 2012 +0900
[2225_xfrout] update the counter object according since the interface of the counter class is changed
commit 38896504bb750809442c77599b0c859a111b9789
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Mon Nov 26 16:15:03 2012 +0900
[2225_xfrout] changed the test condition for "socket/unixdomain/open" counter of Xfrout
because its counter would probably return 0 or 1 depending on timing
commit a0033b21f7c50fe62b954bc501b8381e409966e8
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Mon Nov 26 15:58:09 2012 +0900
[2225_xfrout] rename module name(counter) to class name(Counter)
commit 7016d0a7f2015d23d67b51cb594613a5ed619eba
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Wed Sep 12 16:29:21 2012 +0900
[2252] added checking DataNotFoundError is raised
If un-incremented counter is referred, DataNotFoundError exception
must be raised.
commit a2f8dd5d40c8c6c06ceb7278e6b8990f2a13e7dc
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Sep 28 14:19:52 2012 +0900
[2225] added a new scenario which a slave isn't started
commit 78a13fe9f10d8f2b982564e480ff375316dcf098
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Sep 28 13:17:00 2012 +0900
[2225] added same tests as the above tests
commit b3b2d10880821f7a434c32a0f3a7aabb6a1e5bf6
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Sep 28 11:13:49 2012 +0900
[2225] removed duplicate tests
commit 6f6247afed0403bd27e9a70116445973750a646c
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Mon Sep 3 18:58:47 2012 +0900
[2225] corrected the wrong place to count receive errors of a unix socket
moved the counter from handle_request() to _select_loop() and revised the
related unit test
commit bf26e302febe956fe4216ba1f1f3fc4590255090
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Aug 31 14:48:52 2012 +0900
[2225] added descriptions about unixsocket counters into the manpage
commit b2c11d14542767ab608cf03da71e8f8332fc370b
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Aug 31 12:16:17 2012 +0900
[2225] added checks for statistics unixsocket counters
commit 365e3472c5ea96f3458097ba87f4270972c6eb8d
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Aug 31 14:31:31 2012 +0900
[2225] changed according the changes of the counter class
- implemented a dummy counter class inside of the notifyout test and made it
use it.
commit 8733e86c9483dc5008c54ef37bac0e0455e9b877
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Fri Aug 31 12:13:10 2012 +0900
[2225] changed the format of the debug when revised getstats command
- output content of answer to the stats module with the debug message
commit c06c560406e56d49868c1fe593f5e8f64a00006f
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Mon Nov 19 14:31:36 2012 +0900
[2225] removed obsoleted classes from xfrout.py and xfrout_test.py
XfroutCounter class and TestXfroutCounter are removed
commit edbe6dfa75648a21b426a6075ccfe7ffc4ec2caf
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Thu Nov 22 18:56:28 2012 +0900
[2252] fixed the spec file path of Xfrin for unittesting
SPECFILE_LOCATION was located under the B10_FROM_SOURCE tree
commit a81cba650f8324317dec95b4d95652521a5baf63
Author: Naoki Kambe <kambe at jprs.co.jp>
Date: Mon Nov 19 14:31:31 2012 +0900
[2225] add unixdomain socket as statistics spec
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 3 +-
ChangeLog | 317 ++++
Makefile.am | 7 +
README | 30 +-
configure.ac | 83 +-
doc/Doxyfile | 623 +++++--
doc/devel/02-dhcp.dox | 4 +-
doc/devel/mainpage.dox | 14 +-
doc/guide/bind10-guide.xml | 249 ++-
examples/README | 2 +-
m4macros/ax_boost_for_bind10.m4 | 112 ++
src/bin/auth/auth_messages.mes | 8 +-
src/bin/auth/b10-auth.xml | 48 +-
src/bin/auth/main.cc | 6 +-
src/bin/auth/tests/.gitignore | 2 +
src/bin/auth/tests/Makefile.am | 37 +-
src/bin/auth/tests/auth_srv_unittest.cc | 29 +-
src/bin/auth/tests/gen-query-testdata.py | 98 ++
src/bin/auth/tests/query_inmemory_unittest.cc | 123 --
src/bin/auth/tests/query_unittest.cc | 1108 +++++++-----
src/bin/auth/tests/testdata/.gitignore | 10 +
src/bin/auth/tests/testdata/Makefile.am | 7 +-
src/bin/auth/tests/testdata/example-base-inc.zone | 236 +++
src/bin/auth/tests/testdata/example-base.zone.in | 7 +
.../testdata/example-common-inc-template.zone | 5 +
src/bin/auth/tests/testdata/example-nsec3-inc.zone | 16 +
src/bin/auth/tests/testdata/example-nsec3.zone.in | 8 +
src/bin/auth/tests/testdata/example.zone | 121 --
src/bin/auth/tests/testdata/example.zone.in | 6 +
src/bin/bind10/bind10.xml | 2 +-
src/bin/bind10/bind10_messages.mes | 11 +-
src/bin/bind10/bind10_src.py.in | 160 +-
src/bin/bind10/tests/bind10_test.py.in | 860 +++++++++-
src/bin/bindctl/bindcmd.py | 12 +-
src/bin/bindctl/tests/bindctl_test.py | 16 +-
src/bin/cfgmgr/b10-cfgmgr.py.in | 2 +-
src/bin/cfgmgr/b10-cfgmgr.xml | 2 +-
src/bin/cfgmgr/local_plugins/.gitignore | 1 +
src/bin/cmdctl/.gitignore | 6 +-
src/bin/cmdctl/Makefile.am | 24 +-
src/bin/cmdctl/b10-certgen.cc | 429 +++++
src/bin/cmdctl/b10-certgen.xml | 214 +++
src/bin/cmdctl/b10-cmdctl.xml | 12 +-
src/bin/cmdctl/cmdctl-keyfile.pem | 15 -
src/bin/cmdctl/cmdctl.py.in | 2 +-
src/bin/cmdctl/tests/Makefile.am | 9 +-
src/bin/cmdctl/tests/b10-certgen_test.py | 254 +++
.../testdata/expired-certfile.pem} | 0
.../testdata/mangled-certfile.pem} | 8 +-
src/bin/cmdctl/tests/testdata/noca-certfile.pem | 19 +
src/bin/dbutil/dbutil_messages.mes | 6 +-
src/bin/ddns/ddns.py.in | 2 +-
src/bin/dhcp4/Makefile.am | 4 +-
src/bin/dhcp4/config_parser.cc | 1765 ++++++++++++++++++++
src/bin/dhcp4/config_parser.h | 74 +
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 28 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 4 +-
src/bin/dhcp4/dhcp4.dox | 68 +
src/bin/dhcp4/dhcp4.spec | 212 ++-
src/bin/dhcp4/dhcp4_messages.mes | 101 +-
src/bin/dhcp4/dhcp4_srv.cc | 285 +++-
src/bin/dhcp4/dhcp4_srv.h | 69 +-
src/bin/dhcp4/main.cc | 8 +-
src/bin/dhcp4/tests/Makefile.am | 9 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 1293 ++++++++++++++
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 941 ++++++++++-
src/bin/dhcp4/tests/dhcp4_test.py | 81 +-
src/bin/dhcp4/tests/dhcp4_unittests.cc | 5 +-
src/bin/dhcp6/Makefile.am | 12 +-
src/bin/dhcp6/config_parser.cc | 1311 ++++++++++-----
src/bin/dhcp6/config_parser.h | 133 +-
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 4 +-
src/bin/dhcp6/ctrl_dhcp6_srv.h | 4 +-
src/bin/dhcp6/dhcp6.dox | 40 +-
src/bin/dhcp6/dhcp6.spec | 76 +
src/bin/dhcp6/dhcp6_messages.mes | 90 +-
src/bin/dhcp6/dhcp6_srv.cc | 550 ++++--
src/bin/dhcp6/dhcp6_srv.h | 112 +-
src/bin/dhcp6/main.cc | 10 +-
src/bin/dhcp6/tests/Makefile.am | 9 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 777 ++++++++-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 801 +++++++--
src/bin/dhcp6/tests/dhcp6_test.py | 76 +-
src/bin/loadzone/.gitignore | 1 +
src/bin/loadzone/Makefile.am | 29 +-
src/bin/loadzone/TODO | 13 -
src/bin/loadzone/b10-loadzone.py.in | 94 --
src/bin/loadzone/b10-loadzone.xml | 130 +-
src/bin/loadzone/loadzone.py.in | 296 ++++
src/bin/loadzone/loadzone_messages.mes | 73 +
src/bin/loadzone/run_loadzone.sh.in | 10 +-
src/bin/{cmdctl => loadzone}/tests/Makefile.am | 24 +-
src/bin/loadzone/tests/correct/Makefile.am | 5 +-
src/bin/loadzone/tests/correct/correct_test.sh.in | 18 +-
src/bin/loadzone/tests/correct/example.db | 14 +-
src/bin/loadzone/tests/correct/include.db | 4 +-
src/bin/loadzone/tests/correct/known.test.out | 4 +-
src/bin/loadzone/tests/correct/mix1.db | 4 +-
src/bin/loadzone/tests/correct/mix2.db | 4 +-
src/bin/loadzone/tests/correct/mix2sub2.txt | 4 +-
src/bin/loadzone/tests/correct/ttl1.db | 4 +-
src/bin/loadzone/tests/correct/ttl2.db | 4 +-
src/bin/loadzone/tests/correct/ttlext.db | 4 +-
src/bin/loadzone/tests/error/.gitignore | 1 -
src/bin/loadzone/tests/error/Makefile.am | 28 -
src/bin/loadzone/tests/error/error.known | 11 -
src/bin/loadzone/tests/error/error_test.sh.in | 82 -
src/bin/loadzone/tests/error/formerr1.db | 13 -
src/bin/loadzone/tests/error/formerr2.db | 12 -
src/bin/loadzone/tests/error/formerr3.db | 12 -
src/bin/loadzone/tests/error/formerr4.db | 12 -
src/bin/loadzone/tests/error/formerr5.db | 13 -
src/bin/loadzone/tests/error/include.txt | 1 -
src/bin/loadzone/tests/error/keyerror1.db | 12 -
src/bin/loadzone/tests/error/keyerror2.db | 12 -
src/bin/loadzone/tests/error/keyerror3.db | 13 -
src/bin/loadzone/tests/error/originerr1.db | 11 -
src/bin/loadzone/tests/error/originerr2.db | 12 -
src/bin/loadzone/tests/loadzone_test.py | 334 ++++
.../tests/testdata/broken-example.org.zone | 11 +
.../loadzone/tests/testdata/example-nons.org.zone | 10 +
.../loadzone/tests/testdata/example-nosoa.org.zone | 3 +
src/bin/loadzone/tests/testdata/example.org.zone | 10 +
src/bin/msgq/Makefile.am | 16 +-
src/bin/msgq/msgq.py.in | 354 +++-
src/bin/msgq/msgq.spec | 8 +
src/bin/msgq/msgq.xml | 2 +-
src/bin/msgq/msgq_messages.mes | 106 ++
src/bin/msgq/run_msgq.sh.in | 18 +-
src/bin/msgq/tests/Makefile.am | 2 +
src/bin/msgq/tests/msgq_test.in | 28 -
src/bin/msgq/tests/msgq_test.py | 327 +++-
src/bin/resolver/b10-resolver.xml | 4 +-
src/bin/resolver/main.cc | 12 +-
src/bin/resolver/resolver.h | 6 +-
src/bin/stats/b10-stats-httpd.xml | 8 +-
src/bin/stats/b10-stats.xml | 2 +-
src/bin/stats/stats.py.in | 4 +-
src/bin/stats/stats_httpd.py.in | 4 +-
src/bin/xfrin/b10-xfrin.xml | 10 +-
src/bin/xfrin/tests/xfrin_test.py | 54 +-
src/bin/xfrin/xfrin.py.in | 47 +-
src/bin/xfrin/xfrin_messages.mes | 21 +-
src/bin/xfrout/tests/xfrout_test.py.in | 2 +-
src/bin/xfrout/xfrout.py.in | 2 +-
src/bin/xfrout/xfrout_messages.mes | 7 +-
src/bin/zonemgr/zonemgr.py.in | 5 +-
src/bin/zonemgr/zonemgr_messages.mes | 2 +-
src/lib/asiodns/asiodns_messages.mes | 16 +
src/lib/asiodns/sync_udp_server.cc | 8 +-
src/lib/asiodns/udp_server.cc | 8 +-
src/lib/asiolink/io_address.cc | 17 +-
src/lib/asiolink/io_address.h | 23 +-
src/lib/asiolink/tests/io_address_unittest.cc | 47 +-
src/lib/cc/data.cc | 4 +-
src/lib/cc/data.h | 3 +-
src/lib/config/config_data.cc | 2 +-
src/lib/config/config_data.h | 16 +-
src/lib/config/tests/config_data_unittests.cc | 11 +-
src/lib/datasrc/Makefile.am | 6 +-
src/lib/datasrc/client.cc | 50 +
src/lib/datasrc/client.h | 76 +-
src/lib/datasrc/database.cc | 133 +-
src/lib/datasrc/database.h | 59 +-
src/lib/datasrc/datasrc_messages.mes | 43 +-
src/lib/datasrc/master_loader_callbacks.cc | 83 +
src/lib/datasrc/master_loader_callbacks.h | 67 +
src/lib/datasrc/memory/Makefile.am | 1 +
src/lib/datasrc/memory/memory_client.cc | 55 +-
src/lib/datasrc/memory/rdataset.cc | 4 +-
src/lib/datasrc/memory/rdataset.h | 31 +-
src/lib/datasrc/memory/util_internal.h | 57 +
src/lib/datasrc/memory/zone_data.cc | 24 +-
src/lib/datasrc/memory/zone_data.h | 14 +-
src/lib/datasrc/memory/zone_data_loader.cc | 58 +-
src/lib/datasrc/memory/zone_data_updater.cc | 96 +-
src/lib/datasrc/memory/zone_data_updater.h | 37 +-
src/lib/datasrc/memory/zone_finder.cc | 26 +-
src/lib/datasrc/rrset_collection_base.cc | 71 +
src/lib/datasrc/rrset_collection_base.h | 126 ++
src/lib/datasrc/sqlite3_accessor.cc | 51 +-
src/lib/datasrc/sqlite3_accessor.h | 205 +--
src/lib/datasrc/tests/Makefile.am | 5 +
src/lib/datasrc/tests/client_unittest.cc | 4 +
src/lib/datasrc/tests/database_unittest.cc | 366 +++-
.../datasrc/tests/master_loader_callbacks_test.cc | 151 ++
src/lib/datasrc/tests/memory/Makefile.am | 2 +
.../datasrc/tests/memory/memory_client_unittest.cc | 29 +-
.../tests/memory/rdata_serialization_unittest.cc | 27 +-
src/lib/datasrc/tests/memory/rdataset_unittest.cc | 75 +-
.../datasrc/tests/memory/testdata/2503-test.zone | 13 +
.../datasrc/tests/memory/testdata/2504-test.zone | 10 +
src/lib/datasrc/tests/memory/testdata/Makefile.am | 3 +
.../tests/memory/zone_data_loader_unittest.cc | 65 +
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 34 +-
.../tests/memory/zone_data_updater_unittest.cc | 208 +++
.../datasrc/tests/memory/zone_finder_unittest.cc | 131 ++
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 2 +-
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 125 ++
src/lib/datasrc/tests/testdata/checkwarn.zone | 4 +
...ttest.zone => contexttest-almost-obsolete.zone} | 2 +-
src/lib/datasrc/tests/testdata/contexttest.zone | 7 +
src/lib/datasrc/tests/testdata/novalidate.zone | 3 +
.../datasrc/tests/zone_finder_context_unittest.cc | 44 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 544 ++++++
src/lib/datasrc/zone.h | 35 +
src/lib/datasrc/zone_loader.cc | 221 +++
src/lib/datasrc/zone_loader.h | 238 +++
src/lib/dhcp/Makefile.am | 20 +-
src/lib/dhcp/duid.cc | 34 +-
src/lib/dhcp/duid.h | 57 +-
src/lib/dhcp/hwaddr.cc | 62 +
src/lib/dhcp/hwaddr.h | 67 +
src/lib/dhcp/iface_mgr.cc | 35 +-
src/lib/dhcp/iface_mgr_linux.cc | 2 +-
src/lib/dhcp/libdhcp++.cc | 267 +--
src/lib/dhcp/libdhcp++.h | 60 +-
src/lib/dhcp/option.cc | 97 +-
src/lib/dhcp/option.h | 81 +-
src/lib/dhcp/option4_addrlst.cc | 4 +-
src/lib/dhcp/option4_addrlst.h | 4 +-
src/lib/dhcp/option6_addrlst.cc | 15 +-
src/lib/dhcp/option6_addrlst.h | 4 +-
src/lib/dhcp/option6_ia.cc | 4 +-
src/lib/dhcp/option6_ia.h | 9 +-
src/lib/dhcp/option6_iaaddr.cc | 15 +-
src/lib/dhcp/option_custom.cc | 632 +++++++
src/lib/dhcp/option_custom.h | 358 ++++
src/lib/dhcp/option_data_types.cc | 247 +++
src/lib/dhcp/option_data_types.h | 360 +++-
src/lib/dhcp/option_definition.cc | 480 ++++--
src/lib/dhcp/option_definition.h | 322 ++--
src/lib/dhcp/{option6_int.h => option_int.h} | 49 +-
.../{option6_int_array.h => option_int_array.h} | 58 +-
src/lib/dhcp/pkt4.cc | 185 +-
src/lib/dhcp/pkt4.h | 80 +-
src/lib/dhcp/pkt6.cc | 72 +-
src/lib/dhcp/pkt6.h | 57 +-
src/lib/dhcp/std_option_defs.h | 329 ++++
src/lib/dhcp/tests/Makefile.am | 7 +-
src/lib/dhcp/tests/duid_unittest.cc | 12 +-
src/lib/dhcp/tests/hwaddr_unittest.cc | 126 ++
src/lib/dhcp/tests/iface_mgr_unittest.cc | 9 +-
src/lib/dhcp/tests/libdhcp++_unittest.cc | 719 ++++++--
src/lib/dhcp/tests/option6_ia_unittest.cc | 13 +-
src/lib/dhcp/tests/option_custom_unittest.cc | 1407 ++++++++++++++++
src/lib/dhcp/tests/option_data_types_unittest.cc | 483 ++++++
src/lib/dhcp/tests/option_definition_unittest.cc | 670 +++++---
...ay_unittest.cc => option_int_array_unittest.cc} | 207 ++-
...ion6_int_unittest.cc => option_int_unittest.cc} | 309 +++-
src/lib/dhcp/tests/option_unittest.cc | 23 +
src/lib/dhcp/tests/pkt4_unittest.cc | 92 +-
src/lib/dhcp/tests/pkt6_unittest.cc | 73 +
src/lib/dhcpsrv/Makefile.am | 29 +-
src/lib/dhcpsrv/addr_utilities.cc | 42 +-
src/lib/dhcpsrv/addr_utilities.h | 13 +-
src/lib/dhcpsrv/alloc_engine.cc | 359 +++-
src/lib/dhcpsrv/alloc_engine.h | 121 +-
src/lib/dhcpsrv/cfgmgr.cc | 155 +-
src/lib/dhcpsrv/cfgmgr.h | 129 +-
src/lib/dhcpsrv/database_backends.dox | 15 +-
.../dhcpsrv/dhcp_config_parser.h} | 148 +-
src/lib/dhcpsrv/dhcpdb_create.mysql | 11 +-
.../dhcpsrv/dhcpsrv_log.cc} | 18 +-
src/lib/dhcpsrv/dhcpsrv_log.h | 66 +
src/lib/dhcpsrv/dhcpsrv_messages.mes | 237 +++
src/lib/dhcpsrv/lease_mgr.cc | 85 +-
src/lib/dhcpsrv/lease_mgr.h | 336 ++--
src/lib/dhcpsrv/lease_mgr_factory.cc | 53 +-
src/lib/dhcpsrv/lease_mgr_factory.h | 35 +-
.../{dhcp/libdhcsrv.dox => dhcpsrv/libdhcpsrv.dox} | 0
src/lib/dhcpsrv/memfile_lease_mgr.cc | 143 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 51 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 1258 ++++++++++----
src/lib/dhcpsrv/mysql_lease_mgr.h | 278 ++-
src/lib/dhcpsrv/option_space.cc | 71 +
src/lib/dhcpsrv/option_space.h | 189 +++
src/lib/dhcpsrv/option_space_container.h | 102 ++
src/lib/dhcpsrv/pool.cc | 8 +-
src/lib/dhcpsrv/pool.h | 7 +
src/lib/dhcpsrv/subnet.cc | 118 +-
src/lib/dhcpsrv/subnet.h | 167 +-
src/lib/dhcpsrv/tests/.gitignore | 1 +
src/lib/dhcpsrv/tests/Makefile.am | 2 +
src/lib/dhcpsrv/tests/addr_utilities_unittest.cc | 47 +
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc | 721 +++++++-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 265 ++-
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 137 +-
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 431 ++++-
.../dhcpsrv/tests/memfile_lease_mgr_unittest.cc | 6 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 1048 +++++++++---
src/lib/dhcpsrv/tests/option_space_unittest.cc | 150 ++
src/lib/dhcpsrv/tests/schema_copy.h | 10 +-
src/lib/dhcpsrv/tests/subnet_unittest.cc | 181 +-
src/lib/dhcpsrv/tests/test_utils.cc | 58 +
src/lib/dhcpsrv/tests/test_utils.h | 49 +
src/lib/dhcpsrv/triplet.h | 2 +-
src/lib/dhcpsrv/utils.h | 40 +
src/lib/dns/Makefile.am | 28 +-
src/lib/dns/character_string.cc | 140 --
src/lib/dns/character_string.h | 57 -
src/lib/dns/dns_fwd.h | 64 +
src/lib/dns/gen-rdatacode.py.in | 61 +-
src/lib/dns/master_lexer.cc | 384 ++++-
src/lib/dns/master_lexer.h | 630 +++++--
src/lib/dns/master_lexer_inputsource.cc | 95 +-
src/lib/dns/master_lexer_inputsource.h | 48 +-
src/lib/dns/master_lexer_state.h | 30 +-
src/lib/dns/master_loader.cc | 651 ++++++++
src/lib/dns/master_loader.h | 195 +++
.../dns/master_loader_callbacks.cc} | 26 +-
src/lib/dns/master_loader_callbacks.h | 142 ++
src/lib/dns/python/Makefile.am | 5 +
src/lib/dns/python/pydnspp.cc | 406 ++---
src/lib/dns/python/pydnspp_common.cc | 17 +
src/lib/dns/python/pydnspp_common.h | 13 +
src/lib/dns/python/rrset_collection_python.cc | 426 +++++
src/lib/dns/python/rrset_collection_python.h | 52 +
src/lib/dns/python/rrset_collection_python_inc.cc | 148 ++
src/lib/dns/python/tests/Makefile.am | 2 +
src/lib/dns/python/tests/rdata_python_test.py | 2 +-
.../python/tests/rrset_collection_python_test.py | 140 ++
src/lib/dns/python/tests/tsigkey_python_test.py | 6 +
.../dns/python/tests/zone_checker_python_test.py | 179 ++
src/lib/dns/python/tsigkey_python.cc | 17 +-
src/lib/dns/python/zone_checker_python.cc | 229 +++
.../dns/python/zone_checker_python.h} | 27 +-
src/lib/dns/python/zone_checker_python_inc.cc | 79 +
src/lib/dns/rdata.cc | 145 +-
src/lib/dns/rdata.h | 69 +-
src/lib/dns/rdata/generic/detail/char_string.cc | 177 ++
src/lib/dns/rdata/generic/detail/char_string.h | 111 ++
src/lib/dns/rdata/generic/detail/lexer_util.h | 70 +
src/lib/dns/rdata/generic/detail/txt_like.h | 129 +-
src/lib/dns/rdata/generic/hinfo_13.cc | 142 +-
src/lib/dns/rdata/generic/hinfo_13.h | 31 +-
src/lib/dns/rdata/generic/naptr_35.cc | 273 +--
src/lib/dns/rdata/generic/naptr_35.h | 35 +-
src/lib/dns/rdata/generic/soa_6.cc | 126 +-
src/lib/dns/rdata/generic/soa_6.h | 4 +
src/lib/dns/rdata/generic/spf_99.cc | 26 +-
src/lib/dns/rdata/generic/spf_99.h | 4 +-
src/lib/dns/rdata/generic/txt_16.cc | 18 +-
src/lib/dns/rdata/generic/txt_16.h | 4 +-
src/lib/dns/rdata/in_1/aaaa_28.cc | 27 +-
src/lib/dns/rdata/template.cc | 5 +
src/lib/dns/rrclass-placeholder.h | 55 +-
src/lib/dns/rrclass.cc | 17 +-
src/lib/dns/rrcollator.cc | 110 ++
src/lib/dns/rrcollator.h | 133 ++
src/lib/dns/rrparamregistry-placeholder.cc | 164 +-
src/lib/dns/rrparamregistry.h | 89 +-
src/lib/dns/rrset.h | 3 -
src/lib/dns/rrset_collection.cc | 128 ++
src/lib/dns/rrset_collection.h | 172 ++
src/lib/dns/rrset_collection_base.h | 195 +++
src/lib/dns/rrttl.cc | 64 +-
src/lib/dns/rrttl.h | 69 +-
src/lib/dns/rrtype.cc | 7 +-
src/lib/dns/tests/Makefile.am | 7 +-
src/lib/dns/tests/character_string_unittest.cc | 92 -
.../dns/tests/master_lexer_inputsource_unittest.cc | 51 +-
src/lib/dns/tests/master_lexer_state_unittest.cc | 370 +++-
src/lib/dns/tests/master_lexer_token_unittest.cc | 95 +-
src/lib/dns/tests/master_lexer_unittest.cc | 402 +++++
src/lib/dns/tests/master_loader_callbacks_test.cc | 84 +
src/lib/dns/tests/master_loader_unittest.cc | 955 +++++++++++
src/lib/dns/tests/rdata_afsdb_unittest.cc | 10 +
src/lib/dns/tests/rdata_char_string_unittest.cc | 252 +++
src/lib/dns/tests/rdata_cname_unittest.cc | 6 +
src/lib/dns/tests/rdata_dhcid_unittest.cc | 10 +
src/lib/dns/tests/rdata_dname_unittest.cc | 6 +
src/lib/dns/tests/rdata_dnskey_unittest.cc | 11 +
src/lib/dns/tests/rdata_ds_like_unittest.cc | 10 +
src/lib/dns/tests/rdata_hinfo_unittest.cc | 51 +-
src/lib/dns/tests/rdata_in_a_unittest.cc | 5 +
src/lib/dns/tests/rdata_in_aaaa_unittest.cc | 6 +
src/lib/dns/tests/rdata_minfo_unittest.cc | 6 +
src/lib/dns/tests/rdata_mx_unittest.cc | 10 +
src/lib/dns/tests/rdata_naptr_unittest.cc | 89 +-
src/lib/dns/tests/rdata_ns_unittest.cc | 10 +
src/lib/dns/tests/rdata_nsec3_unittest.cc | 12 +
.../dns/tests/rdata_nsec3param_like_unittest.cc | 11 +
src/lib/dns/tests/rdata_nsec3param_unittest.cc | 7 +
src/lib/dns/tests/rdata_nsec_unittest.cc | 11 +
src/lib/dns/tests/rdata_opt_unittest.cc | 7 +
src/lib/dns/tests/rdata_ptr_unittest.cc | 6 +
src/lib/dns/tests/rdata_rp_unittest.cc | 11 +
src/lib/dns/tests/rdata_rrsig_unittest.cc | 61 +-
src/lib/dns/tests/rdata_soa_unittest.cc | 180 +-
src/lib/dns/tests/rdata_srv_unittest.cc | 11 +
src/lib/dns/tests/rdata_sshfp_unittest.cc | 6 +
src/lib/dns/tests/rdata_tsig_unittest.cc | 10 +
src/lib/dns/tests/rdata_txt_like_unittest.cc | 223 ++-
src/lib/dns/tests/rdata_unittest.cc | 159 +-
src/lib/dns/tests/rdata_unittest.h | 14 +-
src/lib/dns/tests/rrclass_unittest.cc | 10 +-
src/lib/dns/tests/rrcollator_unittest.cc | 214 +++
src/lib/dns/tests/rrparamregistry_unittest.cc | 34 +
src/lib/dns/tests/rrset_collection_unittest.cc | 246 +++
src/lib/dns/tests/rrttl_unittest.cc | 17 +
src/lib/dns/tests/testdata/Makefile.am | 4 +
src/lib/dns/tests/testdata/broken.zone | 3 +
src/lib/dns/tests/testdata/example.org | 17 +
src/lib/dns/tests/testdata/omitcheck.txt | 1 +
src/lib/dns/tests/testdata/origincheck.txt | 5 +
src/lib/dns/tests/testdata/rdata_txt_fromWire1 | 6 +-
src/lib/dns/tests/tsigkey_unittest.cc | 9 +
src/lib/dns/tests/zone_checker_unittest.cc | 352 ++++
src/lib/dns/tsigkey.cc | 12 +-
src/lib/dns/tsigkey.h | 22 +
src/lib/dns/zone_checker.cc | 196 +++
src/lib/dns/zone_checker.h | 162 ++
src/lib/log/Makefile.am | 1 +
src/lib/log/README | 14 +-
src/lib/log/buffer_appender_impl.cc | 96 ++
src/lib/log/buffer_appender_impl.h | 118 ++
src/lib/log/log_messages.mes | 4 +-
src/lib/log/logger_manager.cc | 6 +-
src/lib/log/logger_manager.h | 24 +-
src/lib/log/logger_manager_impl.cc | 92 +-
src/lib/log/logger_manager_impl.h | 50 +-
src/lib/log/logger_support.cc | 4 +-
src/lib/log/logger_support.h | 6 +-
src/lib/log/tests/.gitignore | 2 +
src/lib/log/tests/Makefile.am | 20 +
src/lib/log/tests/buffer_appender_unittest.cc | 146 ++
src/lib/log/tests/buffer_logger_test.cc | 71 +
...er_lock_test.sh.in => buffer_logger_test.sh.in} | 39 +-
src/lib/log/tests/destination_test.sh.in | 19 +-
src/lib/log/tests/init_logger_test.sh.in | 21 +-
src/lib/log/tests/local_file_test.sh.in | 8 +-
src/lib/log/tests/logger_lock_test.sh.in | 3 +-
src/lib/log/tests/severity_test.sh.in | 12 +-
src/lib/python/isc/bind10/component.py | 10 +-
src/lib/python/isc/bind10/special_component.py | 15 +-
src/lib/python/isc/bind10/tests/component_test.py | 32 +-
src/lib/python/isc/config/cfgmgr.py | 10 +-
src/lib/python/isc/datasrc/Makefile.am | 4 +-
src/lib/python/isc/datasrc/__init__.py | 3 +-
src/lib/python/isc/datasrc/client_inc.cc | 62 +
src/lib/python/isc/datasrc/client_python.cc | 75 +
src/lib/python/isc/datasrc/client_python.h | 11 +
src/lib/python/isc/datasrc/datasrc.cc | 72 +-
src/lib/python/isc/datasrc/master.py | 616 -------
src/lib/python/isc/datasrc/tests/.gitignore | 1 +
src/lib/python/isc/datasrc/tests/Makefile.am | 10 +-
src/lib/python/isc/datasrc/tests/datasrc_test.py | 62 +-
src/lib/python/isc/datasrc/tests/master_test.py | 35 -
.../python/isc/datasrc/tests/testdata/example.com | 8 +
.../isc/datasrc/tests/testdata/example.com.ch | 8 +
...mple.com.sqlite3 => example.com.source.sqlite3} | Bin 70656 -> 70656 bytes
.../isc/datasrc/tests/testdata/example.com.sqlite3 | Bin 70656 -> 70656 bytes
.../python/isc/datasrc/tests/zone_loader_test.py | 251 +++
src/lib/python/isc/datasrc/zone_loader_inc.cc | 176 ++
src/lib/python/isc/datasrc/zone_loader_python.cc | 278 +++
.../python/isc/datasrc/zone_loader_python.h} | 26 +-
src/lib/python/isc/ddns/libddns_messages.mes | 2 +-
src/lib/python/isc/log/log.cc | 31 +-
src/lib/python/isc/log/tests/check_output.sh | 2 +-
src/lib/python/isc/log/tests/log_test.py | 22 +
src/lib/python/isc/log_messages/Makefile.am | 4 +
.../python/isc/log_messages/loadzone_messages.py | 1 +
src/lib/python/isc/log_messages/msgq_messages.py | 1 +
src/lib/python/isc/statistics/counters.py | 4 +
src/lib/resolve/resolve_messages.mes | 6 +-
src/lib/server_common/server_common_messages.mes | 4 +-
src/lib/testutils/dnsmessage_test.h | 3 +-
src/lib/testutils/srv_test.cc | 37 +-
src/lib/testutils/srv_test.h | 10 +-
src/lib/util/encode/base_n.cc | 3 +-
src/lib/util/python/gen_wiredata.py.in | 2 +-
tests/lettuce/configurations/xfrin/.gitignore | 1 +
tests/lettuce/features/msgq.feature | 18 +
tests/lettuce/features/xfrin_bind10.feature | 2 +-
tests/system/bindctl/setup.sh | 4 +-
tests/system/bindctl/tests.sh | 4 +
tests/system/conf.sh.in | 5 +-
tests/system/glue/setup.sh.in | 8 +-
tests/system/ixfr/in-1/setup.sh.in | 2 +-
tests/system/ixfr/in-2/setup.sh.in | 2 +-
tests/system/ixfr/in-3/setup.sh.in | 2 +-
tests/system/ixfr/in-4/setup.sh.in | 2 +-
tests/tools/perfdhcp/Makefile.am | 2 +-
tests/tools/perfdhcp/perf_pkt4.cc | 2 +-
tests/tools/perfdhcp/pkt_transform.cc | 10 +-
tests/tools/perfdhcp/templates/Makefile.am | 10 -
tests/tools/perfdhcp/test_control.cc | 20 +-
tests/tools/perfdhcp/tests/.gitignore | 5 +
tests/tools/perfdhcp/tests/Makefile.am | 6 +-
.../tools/perfdhcp/tests/test_control_unittest.cc | 33 +-
.../{templates => tests/testdata}/.gitignore | 0
tests/tools/perfdhcp/tests/testdata/Makefile.am | 4 +
.../testdata}/discover-example.hex | 0
.../testdata}/request4-example.hex | 0
.../testdata}/request6-example.hex | 0
.../testdata}/solicit-example.hex | 0
497 files changed, 42669 insertions(+), 7828 deletions(-)
create mode 100644 m4macros/ax_boost_for_bind10.m4
create mode 100755 src/bin/auth/tests/gen-query-testdata.py
delete mode 100644 src/bin/auth/tests/query_inmemory_unittest.cc
create mode 100644 src/bin/auth/tests/testdata/example-base-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-base.zone.in
create mode 100644 src/bin/auth/tests/testdata/example-common-inc-template.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3.zone.in
delete mode 100644 src/bin/auth/tests/testdata/example.zone
create mode 100644 src/bin/auth/tests/testdata/example.zone.in
create mode 100644 src/bin/cfgmgr/local_plugins/.gitignore
create mode 100644 src/bin/cmdctl/b10-certgen.cc
create mode 100644 src/bin/cmdctl/b10-certgen.xml
delete mode 100644 src/bin/cmdctl/cmdctl-keyfile.pem
create mode 100644 src/bin/cmdctl/tests/b10-certgen_test.py
copy src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/expired-certfile.pem} (100%)
rename src/bin/cmdctl/{cmdctl-certfile.pem => tests/testdata/mangled-certfile.pem} (80%)
create mode 100644 src/bin/cmdctl/tests/testdata/noca-certfile.pem
create mode 100644 src/bin/dhcp4/config_parser.cc
create mode 100644 src/bin/dhcp4/config_parser.h
create mode 100644 src/bin/dhcp4/dhcp4.dox
create mode 100644 src/bin/dhcp4/tests/config_parser_unittest.cc
delete mode 100644 src/bin/loadzone/b10-loadzone.py.in
create mode 100755 src/bin/loadzone/loadzone.py.in
create mode 100644 src/bin/loadzone/loadzone_messages.mes
copy src/bin/{cmdctl => loadzone}/tests/Makefile.am (56%)
delete mode 100644 src/bin/loadzone/tests/error/.gitignore
delete mode 100644 src/bin/loadzone/tests/error/Makefile.am
delete mode 100644 src/bin/loadzone/tests/error/error.known
delete mode 100755 src/bin/loadzone/tests/error/error_test.sh.in
delete mode 100644 src/bin/loadzone/tests/error/formerr1.db
delete mode 100644 src/bin/loadzone/tests/error/formerr2.db
delete mode 100644 src/bin/loadzone/tests/error/formerr3.db
delete mode 100644 src/bin/loadzone/tests/error/formerr4.db
delete mode 100644 src/bin/loadzone/tests/error/formerr5.db
delete mode 100644 src/bin/loadzone/tests/error/include.txt
delete mode 100644 src/bin/loadzone/tests/error/keyerror1.db
delete mode 100644 src/bin/loadzone/tests/error/keyerror2.db
delete mode 100644 src/bin/loadzone/tests/error/keyerror3.db
delete mode 100644 src/bin/loadzone/tests/error/originerr1.db
delete mode 100644 src/bin/loadzone/tests/error/originerr2.db
create mode 100755 src/bin/loadzone/tests/loadzone_test.py
create mode 100644 src/bin/loadzone/tests/testdata/broken-example.org.zone
create mode 100644 src/bin/loadzone/tests/testdata/example-nons.org.zone
create mode 100644 src/bin/loadzone/tests/testdata/example-nosoa.org.zone
create mode 100644 src/bin/loadzone/tests/testdata/example.org.zone
create mode 100644 src/bin/msgq/msgq.spec
create mode 100644 src/bin/msgq/msgq_messages.mes
delete mode 100755 src/bin/msgq/tests/msgq_test.in
create mode 100644 src/lib/datasrc/client.cc
create mode 100644 src/lib/datasrc/master_loader_callbacks.cc
create mode 100644 src/lib/datasrc/master_loader_callbacks.h
create mode 100644 src/lib/datasrc/memory/util_internal.h
create mode 100644 src/lib/datasrc/rrset_collection_base.cc
create mode 100644 src/lib/datasrc/rrset_collection_base.h
create mode 100644 src/lib/datasrc/tests/master_loader_callbacks_test.cc
create mode 100644 src/lib/datasrc/tests/memory/testdata/2503-test.zone
create mode 100644 src/lib/datasrc/tests/memory/testdata/2504-test.zone
create mode 100644 src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
create mode 100644 src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
create mode 100644 src/lib/datasrc/tests/testdata/checkwarn.zone
copy src/lib/datasrc/tests/testdata/{contexttest.zone => contexttest-almost-obsolete.zone} (99%)
create mode 100644 src/lib/datasrc/tests/testdata/novalidate.zone
create mode 100644 src/lib/datasrc/tests/zone_loader_unittest.cc
create mode 100644 src/lib/datasrc/zone_loader.cc
create mode 100644 src/lib/datasrc/zone_loader.h
create mode 100644 src/lib/dhcp/hwaddr.cc
create mode 100644 src/lib/dhcp/hwaddr.h
create mode 100644 src/lib/dhcp/option_custom.cc
create mode 100644 src/lib/dhcp/option_custom.h
create mode 100644 src/lib/dhcp/option_data_types.cc
rename src/lib/dhcp/{option6_int.h => option_int.h} (81%)
rename src/lib/dhcp/{option6_int_array.h => option_int_array.h} (82%)
create mode 100644 src/lib/dhcp/std_option_defs.h
create mode 100644 src/lib/dhcp/tests/hwaddr_unittest.cc
create mode 100644 src/lib/dhcp/tests/option_custom_unittest.cc
create mode 100644 src/lib/dhcp/tests/option_data_types_unittest.cc
rename src/lib/dhcp/tests/{option6_int_array_unittest.cc => option_int_array_unittest.cc} (67%)
rename src/lib/dhcp/tests/{option6_int_unittest.cc => option_int_unittest.cc} (50%)
copy src/{bin/dhcp6/config_parser.h => lib/dhcpsrv/dhcp_config_parser.h} (51%)
copy src/{bin/dhcp4/tests/dhcp4_unittests.cc => lib/dhcpsrv/dhcpsrv_log.cc} (71%)
create mode 100644 src/lib/dhcpsrv/dhcpsrv_log.h
create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
rename src/lib/{dhcp/libdhcsrv.dox => dhcpsrv/libdhcpsrv.dox} (100%)
create mode 100644 src/lib/dhcpsrv/option_space.cc
create mode 100644 src/lib/dhcpsrv/option_space.h
create mode 100644 src/lib/dhcpsrv/option_space_container.h
create mode 100644 src/lib/dhcpsrv/tests/.gitignore
create mode 100644 src/lib/dhcpsrv/tests/option_space_unittest.cc
create mode 100644 src/lib/dhcpsrv/tests/test_utils.cc
create mode 100644 src/lib/dhcpsrv/tests/test_utils.h
create mode 100644 src/lib/dhcpsrv/utils.h
delete mode 100644 src/lib/dns/character_string.cc
delete mode 100644 src/lib/dns/character_string.h
create mode 100644 src/lib/dns/dns_fwd.h
create mode 100644 src/lib/dns/master_loader.cc
create mode 100644 src/lib/dns/master_loader.h
copy src/{bin/dhcp4/tests/dhcp4_unittests.cc => lib/dns/master_loader_callbacks.cc} (63%)
create mode 100644 src/lib/dns/master_loader_callbacks.h
create mode 100644 src/lib/dns/python/rrset_collection_python.cc
create mode 100644 src/lib/dns/python/rrset_collection_python.h
create mode 100644 src/lib/dns/python/rrset_collection_python_inc.cc
create mode 100644 src/lib/dns/python/tests/rrset_collection_python_test.py
create mode 100644 src/lib/dns/python/tests/zone_checker_python_test.py
create mode 100644 src/lib/dns/python/zone_checker_python.cc
copy src/{bin/dhcp4/tests/dhcp4_unittests.cc => lib/dns/python/zone_checker_python.h} (62%)
create mode 100644 src/lib/dns/python/zone_checker_python_inc.cc
create mode 100644 src/lib/dns/rdata/generic/detail/char_string.cc
create mode 100644 src/lib/dns/rdata/generic/detail/char_string.h
create mode 100644 src/lib/dns/rdata/generic/detail/lexer_util.h
create mode 100644 src/lib/dns/rrcollator.cc
create mode 100644 src/lib/dns/rrcollator.h
create mode 100644 src/lib/dns/rrset_collection.cc
create mode 100644 src/lib/dns/rrset_collection.h
create mode 100644 src/lib/dns/rrset_collection_base.h
delete mode 100644 src/lib/dns/tests/character_string_unittest.cc
create mode 100644 src/lib/dns/tests/master_loader_callbacks_test.cc
create mode 100644 src/lib/dns/tests/master_loader_unittest.cc
create mode 100644 src/lib/dns/tests/rdata_char_string_unittest.cc
create mode 100644 src/lib/dns/tests/rrcollator_unittest.cc
create mode 100644 src/lib/dns/tests/rrset_collection_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/broken.zone
create mode 100644 src/lib/dns/tests/testdata/example.org
create mode 100644 src/lib/dns/tests/testdata/omitcheck.txt
create mode 100644 src/lib/dns/tests/testdata/origincheck.txt
create mode 100644 src/lib/dns/tests/zone_checker_unittest.cc
create mode 100644 src/lib/dns/zone_checker.cc
create mode 100644 src/lib/dns/zone_checker.h
create mode 100644 src/lib/log/buffer_appender_impl.cc
create mode 100644 src/lib/log/buffer_appender_impl.h
create mode 100644 src/lib/log/tests/buffer_appender_unittest.cc
create mode 100644 src/lib/log/tests/buffer_logger_test.cc
copy src/lib/log/tests/{logger_lock_test.sh.in => buffer_logger_test.sh.in} (51%)
delete mode 100644 src/lib/python/isc/datasrc/master.py
delete mode 100644 src/lib/python/isc/datasrc/tests/master_test.py
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/example.com
create mode 100644 src/lib/python/isc/datasrc/tests/testdata/example.com.ch
copy src/lib/python/isc/datasrc/tests/testdata/{example.com.sqlite3 => example.com.source.sqlite3} (51%)
create mode 100644 src/lib/python/isc/datasrc/tests/zone_loader_test.py
create mode 100644 src/lib/python/isc/datasrc/zone_loader_inc.cc
create mode 100644 src/lib/python/isc/datasrc/zone_loader_python.cc
copy src/{bin/dhcp4/tests/dhcp4_unittests.cc => lib/python/isc/datasrc/zone_loader_python.h} (64%)
create mode 100644 src/lib/python/isc/log_messages/loadzone_messages.py
create mode 100644 src/lib/python/isc/log_messages/msgq_messages.py
create mode 100644 tests/lettuce/features/msgq.feature
delete mode 100644 tests/tools/perfdhcp/templates/Makefile.am
rename tests/tools/perfdhcp/{templates => tests/testdata}/.gitignore (100%)
create mode 100644 tests/tools/perfdhcp/tests/testdata/Makefile.am
rename tests/tools/perfdhcp/{templates => tests/testdata}/discover-example.hex (100%)
rename tests/tools/perfdhcp/{templates => tests/testdata}/request4-example.hex (100%)
rename tests/tools/perfdhcp/{templates => tests/testdata}/request6-example.hex (100%)
rename tests/tools/perfdhcp/{templates => tests/testdata}/solicit-example.hex (100%)
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index dd6903c..7bc41b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,6 @@ TAGS
/all.info
/coverage-cpp-html
/dns++.pc
-/report.info
+/local.zone.sqlite3
/logger_lockfile
+/report.info
diff --git a/ChangeLog b/ChangeLog
index 2c485b4..ce45dbc 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,320 @@
+549. [func] tomek
+ b10-dhcp6: It is now possible to specify that a configured subnet
+ is reachable locally over specified interface (see "interface"
+ parameter in Subnet6 configuration).
+ (Trac #2596, git a70f6172194a976b514cd7d67ce097bbca3c2798)
+
+548. [func] vorner
+ The message queue daemon now appears on the bus. This has two
+ effects, one is it obeys logging configuration and logs to the
+ correct place like the rest of the modules. The other is it
+ appears in bindctl as module (but it doesn't have any commands or
+ configuration yet).
+ (Trac #2582, git ced31d8c5a0f2ca930b976d3caecfc24fc04634e)
+
+547. [func]* vorner
+ The b10-loadzone now performs more thorough sanity check on the
+ loaded data. Some of the checks are now fatal and zone failing
+ them will be rejected.
+ (Trac #2436, git 48d999f1cb59f308f9f30ba2639521d2a5a85baa)
+
+546. [func] marcin
+ DHCP option definitions can be now created using the
+ Configuration Manager. The option definition specifies
+ the option code, name and the types of the data being
+ carried by the option. The Configuration Manager
+ reports an error on attempt to override standard DHCP
+ option definition.
+ (Trac #2317, git 71e25eb81e58a695cf3bad465c4254b13a50696e)
+
+545. [func] jinmei
+ libdns++: the SOA Rdata class now uses the generic lexer in
+ constructors from text. This means that the MNAME and RNAME of an
+ SOA RR in a zone file can now be non absolute (the origin name
+ in that context will be used), e.g., when loaded by b10-loadzone.
+ (Trac #2500, git 019ca218027a218921519f205139b96025df2bb5)
+
+544. [func] tomek
+ b10-dhcp4: Allocation engine support for IPv4 added. Currently
+ supported operations are server selection (Discover/Offer),
+ address assignment (Request/Ack), address renewal (Request/Ack),
+ and address release (Release). Expired leases can be reused.
+ Some options (e.g. Router Option) are still hardcoded, so the
+ DHCPv4 server is not yet usable, although its address allocation
+ is operational.
+ (Trac #2320, git 60606cabb1c9584700b1f642bf2af21a35c64573)
+
+543. [func]* jelte
+ When calling getFullConfig() as a module, , the configuration is now
+ returned as properly-structured JSON. Previously, the structure had
+ been flattened, with all data being labelled by fully-qualified element
+ names.
+ (Trac #2619, git bed3c88c25ea8f7e951317775e99ebce3340ca22)
+
+542. [func] marcin
+ Created OptionSpace and OptionSpace6 classes to represent DHCP
+ option spaces. The option spaces are used to group instances
+ and definitions of options having uniqe codes. A special type
+ of option space is the so-called "vendor specific option space"
+ which groups sub-options sent within Vendor Encapsulated Options.
+ The new classes are not used yet but they will be used once
+ the creation of option spaces by configuration manager is
+ implemented.
+ (Trac #2313, git 37a27e19be874725ea3d560065e5591a845daa89)
+
+541. [func] marcin
+ Added routines to search for configured DHCP options and their
+ definitions using name of the option space they belong to.
+ New routines are called internally from the DHCPv4 and DHCPv6
+ servers code.
+ (Trac #2315, git 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8)
+
+540. [func] marcin
+ DHCP Option values can be now specified using a string of
+ tokens separated with comma sign. Subsequent tokens are used
+ to set values for corresponding data fields in a particular
+ DHCP option. The format of the token matches the data type
+ of the corresponding option field: e.g. "192.168.2.1" for IPv4
+ address, "5" for integer value etc.
+ (Trac #2545, git 792c129a0785c73dd28fd96a8f1439fe6534a3f1)
+
+539. [func] stephen
+ Add logging to the DHCP server library.
+ (Trac #2524, git b55b8b6686cc80eed41793c53d1779f4de3e9e3c)
+
+538. [bug] muks
+ Added escaping of special characters (double-quotes, semicolon,
+ backslash, etc.) in text-like RRType's toText() implementation.
+ Without this change, some TXT and SPF RDATA were incorrectly
+ stored in SQLite3 datasource as they were not escaped.
+ (Trac #2535, git f516fc484544b7e08475947d6945bc87636d4115)
+
+537. [func] tomek
+ b10-dhcp6: Support for RELEASE message has been added. Clients
+ are now able to release their non-temporary IPv6 addresses.
+ (Trac #2326, git 0974318566abe08d0702ddd185156842c6642424)
+
+536. [build] jinmei
+ Detect a build issue on FreeBSD with g++ 4.2 and Boost installed via
+ FreeBSD ports at ./configure time. This seems to be a bug of
+ FreeBSD ports setup and has been reported to the maintainer:
+ http://www.freebsd.org/cgi/query-pr.cgi?pr=174753
+ Until it's fixed, you need to build BIND 10 for FreeBSD that has
+ this problem with specifying --without-werror, with clang++
+ (development version), or with manually extracted Boost header
+ files (no compiled Boost library is necessary).
+ (Trac #1991, git 6b045bcd1f9613e3835551cdebd2616ea8319a36)
+
+535. [bug] jelte
+ The log4cplus internal logging mechanism has been disabled, and no
+ output from the log4cplus library itself should be printed to
+ stderr anymore. This output can be enabled by using the
+ compile-time option --enable-debug.
+ (Trac #1081, git db55f102b30e76b72b134cbd77bd183cd01f95c0)
+
+534. [func]* vorner
+ The b10-msgq now uses the same logging format as the rest
+ of the system. However, it still doesn't obey the common
+ configuration, as due to technical issues it is not able
+ to read it yet.
+ (git 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb)
+
+bind10-1.0.0-beta released on December 20, 2012
+
+533. [build]* jreed
+ Changed the package name in configure.ac from bind10-devel
+ to bind10. This means the default sub-directories for
+ etc, include, libexec, share, share/doc, and var are changed.
+ If upgrading from a previous version, you may need to move
+ and update your configurations or change references for the
+ old locations.
+ (git bf53fbd4e92ae835280d49fbfdeeebd33e0ce3f2)
+
+532. [func] marcin
+ Implemented configuration of DHCPv4 option values using
+ the configuration manager. In order to set values for the
+ data fields carried by a particular option, the user
+ specifies a string of hexadecimal digits that is converted
+ to binary data and stored in the option buffer. A more
+ user-friendly way of specifying option content is planned.
+ (Trac #2544, git fed1aab5a0f813c41637807f8c0c5f8830d71942)
+
+531. [func] tomek
+ b10-dhcp6: Added support for expired leases. Leases for IPv6
+ addresses that are past their valid lifetime may be recycled, i.e.
+ rellocated to other clients if needed.
+ (Trac #2327, git 62a23854f619349d319d02c3a385d9bc55442d5e)
+
+530. [func]* team
+ b10-loadzone was fully overhauled. It now uses C++-based zone
+ parser and loader library, performing stricter checks, having
+ more complete support for master file formats, producing more
+ helpful logs, is more extendable for various types of data
+ sources, and yet much faster than the old version. In
+ functionality the new version should be generally backwards
+ compatible to the old version, but there are some
+ incompatibilities: name fields of RDATA (in NS, SOA, etc) must
+ be absolute for now; due to the stricter checks some input that was
+ (incorrectly) accepted by the old version may now be rejected;
+ command line options and arguments are not compatible.
+ (Trac #2380, git 689b015753a9e219bc90af0a0b818ada26cc5968)
+
+529. [func]* team
+ The in-memory data source now uses a more complete master
+ file parser to load textual zone files. As of this change
+ it supports multi-line RR representation and more complete
+ support for escaped and quoted strings. It also produces
+ more helpful log messages when there is an error in the zone
+ file. It will be enhanced as more specific tasks in the
+ #2368 meta ticket are completed. The new parser is generally
+ backward compatible to the previous one, but due to the
+ tighter checks some input that has been accepted so far
+ could now be rejected, so it's advisable to check if you
+ use textual zone files directly loaded to memory.
+ (Trac #2470, git c4cf36691115c15440b65cac16f1c7fcccc69521)
+
+528. [func] marcin
+ Implemented definitions for DHCPv4 option definitions identified
+ by option codes: 1 to 63, 77, 81-82, 90-92, 118-119, 124-125.
+ These definitions are now used by the DHCPv4 server to parse
+ options received from a client.
+ (Trac #2526, git 50a73567e8067fdbe4405b7ece5b08948ef87f98)
+
+527. [bug] jelte
+ Fixed a bug in the synchronous UDP server code where unexpected
+ errors from ASIO or the system libraries could cause b10-auth to
+ stop. In asynchronous mode these errors would be ignored
+ completely. Both types have been updated to report the problem with
+ an ERROR log message, drop the packet, and continue service.
+ (Trac #2494, git db92f30af10e6688a7dc117b254cb821e54a6d95)
+
+526. [bug] stephen
+ Miscellaneous fixes to DHCP code including rationalisation of
+ some methods in LeaseMgr and resolving some Doxygen/cppcheck
+ issues.
+ (Trac #2546, git 0140368ed066c722e5d11d7f9cf1c01462cf7e13)
+
+525. [func] tomek
+ b10-dhcp4: DHCPv4 server is now able to parse configuration. It
+ is possible to specify IPv4 subnets with dynamic pools within
+ them. Although configuration is accepted, it is not used yet. This
+ will be implemented shortly.
+ (Trac #2270, git de29c07129d41c96ee0d5eebdd30a1ea7fb9ac8a)
+
+524. [func] tomek
+ b10-dhcp6 is now able to handle RENEW messages. Leases are
+ renewed and REPLY responses are sent back to clients.
+ (Trac #2325, git 7f6c9d057cc0a7a10f41ce7da9c8565b9ee85246)
+
+523. [bug] muks
+ Fixed a problem in inmem NSEC3 lookup (for, instance when using a
+ zone with no non-apex names) which caused exceptions when the zone
+ origin was not added as an explicit NSEC3 record.
+ (Trac #2503, git 6fe86386be0e7598633fe35999112c1a6e3b0370)
+
+522. [func]* jelte
+ Configuration of TSIG keys for b10-xfrin has changed; instead of
+ specifying the full TSIG key (<name>:<base64>:<algo>) it now expects
+ just the name, and uses the global TSIG Key Ring like all the other
+ components (configuration list /tsig_keys/keys).
+ Note: this is not automatically updated, so if you use TSIG in
+ xfrin, you need to update your configuration.
+ (Trac #1351, git e65b7b36f60f14b7abe083da411e6934cdfbae7a)
+
+521. [func] marcin
+ Implemented definitions for DHCPv6 standard options identified
+ by codes up to 48. These definitions are now used by the DHCPv6
+ server to create instances of options being sent to a client.
+ (Trac #2491, git 0a4faa07777189ed9c25211987a1a9b574015a95)
+
+520. [func] jelte
+ The system no longer prints initial log messages to stdout
+ regardless of what logging configuration is present, but it
+ temporarily stores any log messages until the configuration is
+ processed. If there is no specific configuration, or if the
+ configuration cannot be accessed, it will still fall back to stdout.
+ Note that there are still a few instances where output is printed,
+ these shall be addressed separately.
+ Note also that, currently, in case it falls back to stdout (such as
+ when it cannot connect to b10-cfgmgr), all log messages are always
+ printed (including debug messages), regardless of whether -v was
+ used. This shall also be addressed in a future change.
+ (Trac #2445, git 74a0abe5a6d10b28e4a3e360e87b129c232dea68)
+
+519. [bug] muks
+ Fixed a problem in inmem NSEC lookup which caused returning an
+ incorrect NSEC record or (in rare cases) assert failures
+ when a non-existent domain was queried, which was a sub-domain of
+ a domain that existed.
+ (Trac #2504, git 835553eb309d100b062051f7ef18422d2e8e3ae4)
+
+518. [func] stephen
+ Extend DHCP MySQL backend to handle IPv4 addresses.
+ (Trac #2404, git ce7db48d3ff5d5aad12b1da5e67ae60073cb2607)
+
+517. [func] stephen
+ Added IOAddress::toBytes() to get byte representation of address.
+ Also added convenience methods for V4/V6 address determination.
+ (Trac #2396, git c23f87e8ac3ea781b38d688f8f7b58539f85e35a)
+
+516. [bug] marcin
+ Fixed 'make distcheck' failure when running perfdhcp unit tests.
+ The unit tests used to read files from the folder specified
+ with the path relative to current folder, thus when the test was
+ run from a different folder the files could not be found.
+ (Trac #2479, git 4e8325e1b309f1d388a3055ec1e1df98c377f383)
+
+515. [bug] jinmei
+ The in-memory data source now accepts an RRSIG provided without
+ a covered RRset in loading. A subsequent query for its owner name
+ of the covered type would generally result in NXRRSET; if the
+ covered RRset is of type NSEC3, the corresponding NSEC3 processing
+ would result in SERVFAIL.
+ (Trac #2420, git 6744c100953f6def5500bcb4bfc330b9ffba0f5f)
+
+514. [bug] jelte
+ b10-msgq now handles socket errors more gracefully when sending data
+ to clients. It no longer exits with 'broken pipe' errors, and is
+ also better at resending data on temporary error codes from send().
+ (Trac #2398, git 9f6b45ee210a253dca608848a58c824ff5e0d234)
+
+513. [func] marcin
+ Implemented the OptionCustom class for DHCPv4 and DHCPv6.
+ This class represents an option which has a defined
+ structure: a set of data fields of specific types and order.
+ It is used to represent those options that can't be
+ represented by any other specialized class.
+ (Trac #2312, git 28d885b457dda970d9aecc5de018ec1120143a10)
+
+512. [func] jelte
+ Added a new tool b10-certgen, to check and update the self-signed
+ SSL certificate used by b10-cmdctl. The original certificate
+ provided has been removed, and a fresh one is generated upon first
+ build. See the b10-certgen manpage for information on how to update
+ existing installed certificates.
+ (Trac #1044, git 510773dd9057ccf6caa8241e74a7a0b34ca971ab)
+
+511. [bug] stephen
+ Fixed a race condition in the DHCP tests whereby the test program
+ spawned a subprocess and attempted to read (without waiting) from
+ the interconnecting pipe before the subprocess had written
+ anything. The lack of output was being interpreted as a test
+ failure.
+ (Trac #2410, git f53e65cdceeb8e6da4723730e4ed0a17e4646579)
+
+510. [func] marcin
+ DHCP option instances can be created using a collection of strings.
+ Each string represents a value of a particular data field within
+ an option. The data field values, given as strings, are validated
+ against the actual types of option fields specified in the options
+ definitions.
+ (Trac #2490, git 56cfd6612fcaeae9acec4a94e1e5f1a88142c44d)
+
+509. [func] muks
+ Log messages now include the pid of the process that logged the
+ message.
+ (Trac #1745, git fc8bbf3d438e8154e7c2bdd322145a7f7854dc6a)
+
508. [bug] stephen
Split the DHCP library into two directories, each with its own
Makefile. This properly solves the problem whereby a "make"
diff --git a/Makefile.am b/Makefile.am
index 2f3ce85..fe995a7 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -20,6 +20,13 @@ dist_doc_DATA = AUTHORS COPYING ChangeLog README
.PHONY: check-valgrind check-valgrind-suppress
+install-exec-hook:
+ - at echo -e "\033[1;33m" # switch to yellow color text
+ @echo "NOTE: BIND 10 does not automatically start DNS services when it is run"
+ @echo " in its default configuration. Please see the Guide for information"
+ @echo " on how to configure these services to be started automatically."
+ - at echo -e "\033[m" # switch back to normal
+
check-valgrind:
if HAVE_VALGRIND
@VALGRIND_COMMAND="$(VALGRIND) -q --gen-suppressions=all --track-origins=yes --num-callers=48 --leak-check=full --fullpath-after=" \
diff --git a/README b/README
index e9d052c..306b7d8 100644
--- a/README
+++ b/README
@@ -7,16 +7,20 @@ DHCP. BIND 10 is written in C++ and Python and provides a modular
environment for serving, maintaining, and developing DNS and DHCP.
This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
-backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
-remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
-AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
-secondary manager, b10-stats statistics collection and reporting
-daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
-b10-host DNS lookup utility, and a new libdns++ library for C++
-with a python wrapper. BIND 10 also provides experimental DHCPv4
-and DHCPv6 servers, b10-dhcp4 and b10-dhcp6, a portable DHCP library,
-libdhcp++, and a DHCP benchmarking tool, perfdhcp.
+bus, b10-cmdctl remote control daemon, b10-cfgmgr configuration
+manager, b10-stats statistics collection and reporting daemon, and
+b10-stats-httpd for HTTP access to XML-formatted stats.
+
+For DNS services, it provides the b10-auth authoritative DNS server
+(with SQLite3 and in-memory backends), b10-resolver recursive or
+forwarding DNS server, b10-xfrin IXFR/AXFR inbound service, b10-xfrout
+outgoing IXFR/AXFR service, b10-zonemgr secondary manager, libdns++
+library for C++ with a python wrapper, and many tests and example
+programs.
+
+BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
+b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
+a DHCP benchmarking tool, perfdhcp.
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
@@ -58,5 +62,7 @@ For operating system specific tips see the wiki at:
Please see the wiki and the doc/ directory for various documentation.
-The BIND 10 suite is started by running "bind10". Note that the
-default configuration does not run any DNS or DHCP servers.
+The BIND 10 suite is started by running "bind10". Note that the default
+configuration does not start any DNS or DHCP services. Please see the
+Guide for information on how to configure these services to be started
+automatically.
diff --git a/configure.ac b/configure.ac
index 1e21269..9ae7a30 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20120817, bind10-dev at isc.org)
+AC_INIT(bind10, 20121219, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE([foreign])
m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])dnl be backward compatible
@@ -166,8 +166,6 @@ fi
fi dnl GXX = yes
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
# allow building programs with static link. we need to make it selective
# because loadable modules cannot be statically linked.
AC_ARG_ENABLE([static-link],
@@ -705,7 +703,6 @@ fi
AC_SUBST(BOTAN_LDFLAGS)
AC_SUBST(BOTAN_LIBS)
AC_SUBST(BOTAN_INCLUDES)
-
# Even though chances are high we already performed a real compilation check
# in the search for the right (pkg)config data, we try again here, to
# be sure.
@@ -839,57 +836,22 @@ LIBS=$LIBS_SAVED
#
# Configure Boost header path
#
-# If explicitly specified, use it.
-AC_ARG_WITH([boost-include],
- AC_HELP_STRING([--with-boost-include=PATH],
- [specify exact directory for Boost headers]),
- [boost_include_path="$withval"])
-# If not specified, try some common paths.
-if test -z "$with_boost_include"; then
- boostdirs="/usr/local /usr/pkg /opt /opt/local"
- for d in $boostdirs
- do
- if test -f $d/include/boost/shared_ptr.hpp; then
- boost_include_path=$d/include
- break
- fi
- done
+AX_BOOST_FOR_BIND10
+# Boost offset_ptr is required in one library and not optional right now, so
+# we unconditionally fail here if it doesn't work.
+if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+ AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])
fi
-CPPFLAGS_SAVES="$CPPFLAGS"
-if test "${boost_include_path}" ; then
- BOOST_INCLUDES="-I${boost_include_path}"
- CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+
+# There's a known bug in FreeBSD ports for Boost that would trigger a false
+# warning in build with g++ and -Werror (we exclude clang++ explicitly to
+# avoid unexpected false positives).
+if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGPP = "no"; then
+ AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
- AC_MSG_ERROR([Missing required header files.]))
-
-# Detect whether Boost tries to use threads by default, and, if not,
-# make it sure explicitly. In some systems the automatic detection
-# may depend on preceding header files, and if inconsistency happens
-# it could lead to a critical disruption.
-AC_MSG_CHECKING([whether Boost tries to use threads])
-AC_TRY_COMPILE([
-#include <boost/config.hpp>
-#ifdef BOOST_HAS_THREADS
-#error "boost will use threads"
-#endif],,
-[AC_MSG_RESULT(no)
- CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
-[AC_MSG_RESULT(yes)])
-
-# Boost offset_ptr is required in one library (not optional right now), and
-# it's known it doesn't compile on some platforms, depending on boost version,
-# its local configuration, and compiler.
-AC_MSG_CHECKING([Boost offset_ptr compiles])
-AC_TRY_COMPILE([
-#include <boost/interprocess/offset_ptr.hpp>
-],,
-[AC_MSG_RESULT(yes)],
-[AC_MSG_RESULT(no)
- AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])])
-CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
-AC_SUBST(BOOST_INCLUDES)
+# Add some default CPP flags needed for Boost, identified by the AX macro.
+CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
# I can't get some of the #include <asio.hpp> right without this
# TODO: find the real cause of asio/boost wanting pthreads
@@ -904,11 +866,12 @@ AC_SUBST(MULTITHREADING_FLAG)
#
GTEST_LDFLAGS=
GTEST_LDADD=
-# TODO: set DISTCHECK_GTEST_CONFIGURE_FLAG for --with-gtest too
DISTCHECK_GTEST_CONFIGURE_FLAG=
if test "x$enable_gtest" = "xyes" ; then
+ DISTCHECK_GTEST_CONFIGURE_FLAG="--with-gtest=$gtest_path"
+
if test -n "$with_gtest_source" ; then
if test "x$GTEST_SOURCE" = "xyes" ; then
@@ -1176,8 +1139,8 @@ AC_CONFIG_FILES([Makefile
src/bin/dbutil/tests/Makefile
src/bin/dbutil/tests/testdata/Makefile
src/bin/loadzone/Makefile
+ src/bin/loadzone/tests/Makefile
src/bin/loadzone/tests/correct/Makefile
- src/bin/loadzone/tests/error/Makefile
src/bin/msgq/Makefile
src/bin/msgq/tests/Makefile
src/bin/auth/Makefile
@@ -1311,7 +1274,7 @@ AC_CONFIG_FILES([Makefile
tests/tools/badpacket/tests/Makefile
tests/tools/perfdhcp/Makefile
tests/tools/perfdhcp/tests/Makefile
- tests/tools/perfdhcp/templates/Makefile
+ tests/tools/perfdhcp/tests/testdata/Makefile
dns++.pc
])
AC_OUTPUT([doc/version.ent
@@ -1352,15 +1315,16 @@ AC_OUTPUT([doc/version.ent
src/bin/bindctl/tests/bindctl_test
src/bin/loadzone/run_loadzone.sh
src/bin/loadzone/tests/correct/correct_test.sh
- src/bin/loadzone/tests/error/error_test.sh
- src/bin/loadzone/b10-loadzone.py
+ src/bin/loadzone/loadzone.py
src/bin/usermgr/run_b10-cmdctl-usermgr.sh
src/bin/usermgr/b10-cmdctl-usermgr.py
src/bin/msgq/msgq.py
- src/bin/msgq/tests/msgq_test
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
src/bin/auth/spec_config.h.pre
+ src/bin/auth/tests/testdata/example.zone
+ src/bin/auth/tests/testdata/example-base.zone
+ src/bin/auth/tests/testdata/example-nsec3.zone
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/tests/process_rename_test.py
@@ -1378,6 +1342,7 @@ AC_OUTPUT([doc/version.ent
src/lib/log/tests/console_test.sh
src/lib/log/tests/destination_test.sh
src/lib/log/tests/init_logger_test.sh
+ src/lib/log/tests/buffer_logger_test.sh
src/lib/log/tests/local_file_test.sh
src/lib/log/tests/logger_lock_test.sh
src/lib/log/tests/severity_test.sh
@@ -1419,11 +1384,9 @@ AC_OUTPUT([doc/version.ent
chmod +x src/bin/bindctl/run_bindctl.sh
chmod +x src/bin/loadzone/run_loadzone.sh
chmod +x src/bin/loadzone/tests/correct/correct_test.sh
- chmod +x src/bin/loadzone/tests/error/error_test.sh
chmod +x src/bin/sysinfo/run_sysinfo.sh
chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
chmod +x src/bin/msgq/run_msgq.sh
- chmod +x src/bin/msgq/tests/msgq_test
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/log/tests/console_test.sh
chmod +x src/lib/log/tests/destination_test.sh
diff --git a/doc/Doxyfile b/doc/Doxyfile
index cc3b595..5c071c1 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -1,14 +1,14 @@
-# Doxyfile 1.6.1
+# Doxyfile 1.8.2
# This file describes the settings to be used by the documentation system
-# doxygen (www.doxygen.org) for a project
+# doxygen (www.doxygen.org) for a project.
#
-# All text after a hash (#) is considered a comment and will be ignored
+# All text after a hash (#) is considered a comment and will be ignored.
# The format is:
# TAG = value [value, ...]
# For lists items can also be appended using:
# TAG += value [value, ...]
-# Values that contain spaces should be placed between quotes (" ")
+# Values that contain spaces should be placed between quotes (" ").
#---------------------------------------------------------------------------
# Project related configuration options
@@ -22,8 +22,9 @@
DOXYFILE_ENCODING = UTF-8
-# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
-# by quotes) that should identify the project.
+# The PROJECT_NAME tag is a single word (or sequence of words) that should
+# identify the project. Note that if you do not use Doxywizard you need
+# to put quotes around the project name if it contains spaces.
PROJECT_NAME = BIND10
@@ -31,12 +32,21 @@ PROJECT_NAME = BIND10
# This could be handy for archiving the generated documentation or
# if some version control system is used.
-# Currently this variable is overwritten (see devel target in Makefile.am)
-# If the number of parameters to overwrite increases, we should generate
-# Doxyfile (rename it to Doxyfile.in and generate during configure phase)
-
PROJECT_NUMBER =
+# Using the PROJECT_BRIEF tag one can provide an optional one line description
+# for a project that appears at the top of each page and should give viewer
+# a quick idea about the purpose of the project. Keep the description short.
+
+PROJECT_BRIEF =
+
+# With the PROJECT_LOGO tag one can specify an logo or icon that is
+# included in the documentation. The maximum height of the logo should not
+# exceed 55 pixels and the maximum width should not exceed 200 pixels.
+# Doxygen will copy the logo to the output directory.
+
+PROJECT_LOGO =
+
# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute)
# base path where the generated documentation will be put.
# If a relative path is entered, it will be relative to the location
@@ -61,7 +71,7 @@ CREATE_SUBDIRS = YES
# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German,
# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English
# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian,
-# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak,
+# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak,
# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese.
OUTPUT_LANGUAGE = English
@@ -116,7 +126,9 @@ FULL_PATH_NAMES = No
# only done if one of the specified strings matches the left-hand part of
# the path. The tag can be used to show relative paths in the file list.
# If left blank the directory from which doxygen is run is used as the
-# path to strip.
+# path to strip. Note that you specify absolute paths here, but also
+# relative paths, which will be relative from the directory where doxygen is
+# started.
STRIP_FROM_PATH =
@@ -130,7 +142,7 @@ STRIP_FROM_PATH =
STRIP_FROM_INC_PATH =
# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter
-# (but less readable) file names. This can be useful is your file systems
+# (but less readable) file names. This can be useful if your file system
# doesn't support long names like on DOS, Mac, or CD-ROM.
SHORT_NAMES = NO
@@ -185,6 +197,13 @@ TAB_SIZE = 4
ALIASES =
+# This tag can be used to specify a number of word-keyword mappings (TCL only).
+# A mapping has the form "name=value". For example adding
+# "class=itcl::class" will allow you to use the command class in the
+# itcl::class meaning.
+
+TCL_SUBST =
+
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C
# sources only. Doxygen will then generate output that is more tailored for C.
# For instance, some of the names that are used will be different. The list
@@ -211,25 +230,43 @@ OPTIMIZE_FOR_FORTRAN = NO
OPTIMIZE_OUTPUT_VHDL = NO
-# Doxygen selects the parser to use depending on the extension of the files it parses.
-# With this tag you can assign which parser to use for a given extension.
-# Doxygen has a built-in mapping, but you can override or extend it using this tag.
-# The format is ext=language, where ext is a file extension, and language is one of
-# the parsers supported by doxygen: IDL, Java, Javascript, C#, C, C++, D, PHP,
-# Objective-C, Python, Fortran, VHDL, C, C++. For instance to make doxygen treat
-# .inc files as Fortran files (default is PHP), and .f files as C (default is Fortran),
-# use: inc=Fortran f=C. Note that for custom extensions you also need to set FILE_PATTERNS otherwise the files are not read by doxygen.
+# Doxygen selects the parser to use depending on the extension of the files it
+# parses. With this tag you can assign which parser to use for a given
+# extension. Doxygen has a built-in mapping, but you can override or extend it
+# using this tag. The format is ext=language, where ext is a file extension,
+# and language is one of the parsers supported by doxygen: IDL, Java,
+# Javascript, CSharp, C, C++, D, PHP, Objective-C, Python, Fortran, VHDL, C,
+# C++. For instance to make doxygen treat .inc files as Fortran files (default
+# is PHP), and .f files as C (default is Fortran), use: inc=Fortran f=C. Note
+# that for custom extensions you also need to set FILE_PATTERNS otherwise the
+# files are not read by doxygen.
EXTENSION_MAPPING =
+# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all
+# comments according to the Markdown format, which allows for more readable
+# documentation. See http://daringfireball.net/projects/markdown/ for details.
+# The output of markdown processing is further processed by doxygen, so you
+# can mix doxygen, HTML, and XML commands with Markdown formatting.
+# Disable only in case of backward compatibilities issues.
+
+MARKDOWN_SUPPORT = YES
+
+# When enabled doxygen tries to link words that correspond to documented classes,
+# or namespaces to their corresponding documentation. Such a link can be
+# prevented in individual cases by by putting a % sign in front of the word or
+# globally by setting AUTOLINK_SUPPORT to NO.
+
+AUTOLINK_SUPPORT = YES
+
# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want
# to include (a tag file for) the STL sources as input, then you should
# set this tag to YES in order to let doxygen match functions declarations and
# definitions whose arguments contain STL classes (e.g. func(std::string); v.s.
-# func(std::string) {}). This also make the inheritance and collaboration
+# func(std::string) {}). This also makes the inheritance and collaboration
# diagrams that involve STL classes more complete and accurate.
-BUILTIN_STL_SUPPORT = NO
+BUILTIN_STL_SUPPORT = YES
# If you use Microsoft's C++/CLI language, you should set this option to YES to
# enable parsing support.
@@ -242,12 +279,7 @@ CPP_CLI_SUPPORT = NO
SIP_SUPPORT = NO
-# For Microsoft's IDL there are propget and propput attributes to indicate getter
-# and setter methods for a property. Setting this option to YES (the default)
-# will make doxygen to replace the get and set methods by a property in the
-# documentation. This will only work if the methods are indeed getting or
-# setting a simple type. If this is not the case, or you want to show the
-# methods anyway, you should set this option to NO.
+# For Microsoft's IDL there are propget and propput attributes to indicate getter and setter methods for a property. Setting this option to YES (the default) will make doxygen replace the get and set methods by a property in the documentation. This will only work if the methods are indeed getting or setting a simple type. If this is not the case, or you want to show the methods anyway, you should set this option to NO.
IDL_PROPERTY_SUPPORT = YES
@@ -266,6 +298,22 @@ DISTRIBUTE_GROUP_DOC = NO
SUBGROUPING = YES
+# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and
+# unions are shown inside the group in which they are included (e.g. using
+# @ingroup) instead of on a separate page (for HTML and Man pages) or
+# section (for LaTeX and RTF).
+
+INLINE_GROUPED_CLASSES = NO
+
+# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and
+# unions with only public data fields will be shown inline in the documentation
+# of the scope in which they are defined (i.e. file, namespace, or group
+# documentation), provided this scope is documented. If set to NO (the default),
+# structs, classes, and unions are shown on a separate page (for HTML and Man
+# pages) or section (for LaTeX and RTF).
+
+INLINE_SIMPLE_STRUCTS = NO
+
# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum
# is documented as struct, union, or enum with the name of the typedef. So
# typedef struct TypeS {} TypeT, will appear in the documentation as a struct
@@ -282,16 +330,27 @@ TYPEDEF_HIDES_STRUCT = NO
# For small to medium size projects (<1000 input files) the default value is
# probably good enough. For larger projects a too small cache size can cause
# doxygen to be busy swapping symbols to and from disk most of the time
-# causing a significant performance penality.
+# causing a significant performance penalty.
# If the system has enough physical memory increasing the cache will improve the
# performance by keeping more symbols in memory. Note that the value works on
# a logarithmic scale so increasing the size by one will roughly double the
# memory usage. The cache size is given by this formula:
# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0,
-# corresponding to a cache size of 2^16 = 65536 symbols
+# corresponding to a cache size of 2^16 = 65536 symbols.
SYMBOL_CACHE_SIZE = 0
+# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be
+# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given
+# their name and scope. Since this can be an expensive process and often the
+# same symbol appear multiple times in the code, doxygen keeps a cache of
+# pre-resolved symbols. If the cache is too small doxygen will become slower.
+# If the cache is too large, memory is wasted. The cache size is given by this
+# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0,
+# corresponding to a cache size of 2^16 = 65536 symbols.
+
+LOOKUP_CACHE_SIZE = 0
+
#---------------------------------------------------------------------------
# Build related configuration options
#---------------------------------------------------------------------------
@@ -308,6 +367,11 @@ EXTRACT_ALL = YES
EXTRACT_PRIVATE = NO
+# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal
+# scope will be included in the documentation.
+
+EXTRACT_PACKAGE = NO
+
# If the EXTRACT_STATIC tag is set to YES all static members of a file
# will be included in the documentation.
@@ -330,7 +394,7 @@ EXTRACT_LOCAL_METHODS = NO
# extracted and appear in the documentation as a namespace called
# 'anonymous_namespace{file}', where file will be replaced with the base
# name of the file that contains the anonymous namespace. By default
-# anonymous namespace are hidden.
+# anonymous namespaces are hidden.
EXTRACT_ANON_NSPACES = NO
@@ -376,7 +440,7 @@ INTERNAL_DOCS = NO
# in case and if your file system supports case sensitive file names. Windows
# and Mac users are advised to set this option to NO.
-CASE_SENSE_NAMES = NO
+CASE_SENSE_NAMES = YES
# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen
# will show members with their full class and namespace scopes in the
@@ -388,12 +452,18 @@ HIDE_SCOPE_NAMES = NO
# will put a list of the files that are included by a file in the documentation
# of that file.
-SHOW_INCLUDE_FILES = NO
+SHOW_INCLUDE_FILES = YES
+
+# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen
+# will list include files with double quotes in the documentation
+# rather than with sharp brackets.
+
+FORCE_LOCAL_INCLUDES = NO
# If the INLINE_INFO tag is set to YES (the default) then a tag [inline]
# is inserted in the documentation for inline members.
-INLINE_INFO = NO
+INLINE_INFO = YES
# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen
# will sort the (detailed) documentation of file and class members
@@ -407,17 +477,23 @@ SORT_MEMBER_DOCS = YES
# by member name. If set to NO (the default) the members will appear in
# declaration order.
-SORT_BRIEF_DOCS = NO
+SORT_BRIEF_DOCS = YES
-# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the (brief and detailed) documentation of class members so that constructors and destructors are listed first. If set to NO (the default) the constructors will appear in the respective orders defined by SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
+# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen
+# will sort the (brief and detailed) documentation of class members so that
+# constructors and destructors are listed first. If set to NO (the default)
+# the constructors will appear in the respective orders defined by
+# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS.
+# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO
+# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO.
-SORT_MEMBERS_CTORS_1ST = NO
+SORT_MEMBERS_CTORS_1ST = YES
# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the
# hierarchy of group names into alphabetical order. If set to NO (the default)
# the group names will appear in their defined order.
-SORT_GROUP_NAMES = NO
+SORT_GROUP_NAMES = YES
# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be
# sorted by fully-qualified names, including namespaces. If set to
@@ -429,6 +505,15 @@ SORT_GROUP_NAMES = NO
SORT_BY_SCOPE_NAME = NO
+# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to
+# do proper type resolution of all parameters of a function it will reject a
+# match between the prototype and the implementation of a member function even
+# if there is only one candidate or it is obvious which candidate to choose
+# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen
+# will still accept a match between prototype and implementation in such cases.
+
+STRICT_PROTO_MATCHING = NO
+
# The GENERATE_TODOLIST tag can be used to enable (YES) or
# disable (NO) the todo list. This list is created by putting \todo
# commands in the documentation.
@@ -459,10 +544,10 @@ GENERATE_DEPRECATEDLIST= YES
ENABLED_SECTIONS =
# The MAX_INITIALIZER_LINES tag determines the maximum number of lines
-# the initial value of a variable or define consists of for it to appear in
+# the initial value of a variable or macro consists of for it to appear in
# the documentation. If the initializer consists of more lines than specified
# here it will be hidden. Use a value of 0 to hide initializers completely.
-# The appearance of the initializer of individual variables and defines in the
+# The appearance of the initializer of individual variables and macros in the
# documentation can be controlled using \showinitializer or \hideinitializer
# command in the documentation regardless of this setting.
@@ -474,12 +559,6 @@ MAX_INITIALIZER_LINES = 30
SHOW_USED_FILES = YES
-# If the sources in your project are distributed over multiple directories
-# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy
-# in the documentation. The default is NO.
-
-SHOW_DIRECTORIES = NO
-
# Set the SHOW_FILES tag to NO to disable the generation of the Files page.
# This will remove the Files entry from the Quick Index and from the
# Folder Tree View (if specified). The default is YES.
@@ -503,15 +582,25 @@ SHOW_NAMESPACES = YES
FILE_VERSION_FILTER =
-# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by
-# doxygen. The layout file controls the global structure of the generated output files
-# in an output format independent way. The create the layout file that represents
-# doxygen's defaults, run doxygen with the -l option. You can optionally specify a
-# file name after the option, if omitted DoxygenLayout.xml will be used as the name
-# of the layout file.
+# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed
+# by doxygen. The layout file controls the global structure of the generated
+# output files in an output format independent way. To create the layout file
+# that represents doxygen's defaults, run doxygen with the -l option.
+# You can optionally specify a file name after the option, if omitted
+# DoxygenLayout.xml will be used as the name of the layout file.
LAYOUT_FILE =
+# The CITE_BIB_FILES tag can be used to specify one or more bib files
+# containing the references data. This must be a list of .bib files. The
+# .bib extension is automatically appended if omitted. Using this command
+# requires the bibtex tool to be installed. See also
+# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style
+# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this
+# feature you need bibtex and perl available in the search path.
+
+CITE_BIB_FILES =
+
#---------------------------------------------------------------------------
# configuration options related to warning and progress messages
#---------------------------------------------------------------------------
@@ -519,7 +608,7 @@ LAYOUT_FILE =
# The QUIET tag can be used to turn on/off the messages that are generated
# by doxygen. Possible values are YES and NO. If left blank NO is used.
-QUIET = NO
+QUIET = YES
# The WARNINGS tag can be used to turn on/off the warning messages that are
# generated by doxygen. Possible values are YES and NO. If left blank
@@ -540,7 +629,7 @@ WARN_IF_UNDOCUMENTED = YES
WARN_IF_DOC_ERROR = YES
-# This WARN_NO_PARAMDOC option can be abled to get warnings for
+# The WARN_NO_PARAMDOC option can be enabled to get warnings for
# functions that are documented, but have no documentation for their parameters
# or return value. If set to NO (the default) doxygen will only warn about
# wrong or incomplete parameter documentation, but not about the absence of
@@ -572,16 +661,36 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/exceptions ../src/lib/cc \
- ../src/lib/config ../src/lib/cryptolink ../src/lib/dns \
- ../src/lib/datasrc ../src/lib/datasrc/memory \
- ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log \
- ../src/lib/log/compiler ../src/lib/asiolink/ ../src/lib/nsas \
- ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/ \
- ../src/bin/sockcreator/ ../src/lib/util/ ../src/lib/util/io/ \
- ../src/lib/util/threads/ ../src/lib/resolve ../src/lib/acl \
- ../src/lib/statistics ../src/bin/dhcp6 ../src/lib/dhcp ../src/bin/dhcp4 \
- ../tests/tools/perfdhcp devel
+INPUT = ../src/lib/exceptions \
+ ../src/lib/cc \
+ ../src/lib/config \
+ ../src/lib/cryptolink \
+ ../src/lib/dns \
+ ../src/lib/datasrc \
+ ../src/lib/datasrc/memory \
+ ../src/bin/auth \
+ ../src/bin/resolver \
+ ../src/lib/bench \
+ ../src/lib/log \
+ ../src/lib/log/compiler \
+ ../src/lib/asiolink/ \
+ ../src/lib/nsas \
+ ../src/lib/testutils \
+ ../src/lib/cache \
+ ../src/lib/server_common/ \
+ ../src/bin/sockcreator/ \
+ ../src/lib/util/ \
+ ../src/lib/util/io/ \
+ ../src/lib/util/threads/ \
+ ../src/lib/resolve \
+ ../src/lib/acl \
+ ../src/lib/statistics \
+ ../src/bin/dhcp6 \
+ ../src/lib/dhcp \
+ ../src/lib/dhcpsrv \
+ ../src/bin/dhcp4 \
+ ../tests/tools/perfdhcp \
+ devel
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
@@ -595,10 +704,15 @@ INPUT_ENCODING = UTF-8
# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp
# and *.h) to filter out the source-files in the directories. If left
# blank the following patterns are tested:
-# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx
-# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh
+# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py
+# *.f90 *.f *.for *.vhd *.vhdl
-FILE_PATTERNS = *.c *.cc *.h *.hpp *.dox
+FILE_PATTERNS = *.c \
+ *.cc \
+ *.h \
+ *.hpp \
+ *.dox
# The RECURSIVE tag can be used to turn specify whether or not subdirectories
# should be searched for input files as well. Possible values are YES and NO.
@@ -606,14 +720,16 @@ FILE_PATTERNS = *.c *.cc *.h *.hpp *.dox
RECURSIVE = NO
-# The EXCLUDE tag can be used to specify files and/or directories that should
+# The EXCLUDE tag can be used to specify files and/or directories that should be
# excluded from the INPUT source files. This way you can easily exclude a
# subdirectory from a directory tree whose root is specified with the INPUT tag.
+# Note that relative paths are relative to the directory from which doxygen is
+# run.
EXCLUDE =
-# The EXCLUDE_SYMLINKS tag can be used select whether or not files or
-# directories that are symbolic links (a Unix filesystem feature) are excluded
+# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
+# directories that are symbolic links (a Unix file system feature) are excluded
# from the input.
EXCLUDE_SYMLINKS = NO
@@ -624,7 +740,8 @@ EXCLUDE_SYMLINKS = NO
# against the file with absolute path, so to exclude all test directories
# for example use the pattern */test/*
-EXCLUDE_PATTERNS = */*-placeholder.* */cpp/*.py
+EXCLUDE_PATTERNS = */*-placeholder.* \
+ */cpp/*.py
# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names
# (namespaces, classes, functions, etc.) that should be excluded from the
@@ -677,8 +794,8 @@ INPUT_FILTER =
# filter if there is a match.
# The filters are a list of the form:
# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further
-# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER
-# is applied to all files.
+# info on how filters are used. If FILTER_PATTERNS is empty or if
+# non of the patterns match the file name, INPUT_FILTER is applied.
FILTER_PATTERNS =
@@ -688,6 +805,14 @@ FILTER_PATTERNS =
FILTER_SOURCE_FILES = NO
+# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file
+# pattern. A pattern will override the setting for FILTER_PATTERN (if any)
+# and it is also possible to disable source filtering for a specific pattern
+# using *.ext= (so without naming a filter). This option only has effect when
+# FILTER_SOURCE_FILES is enabled.
+
+FILTER_SOURCE_PATTERNS =
+
#---------------------------------------------------------------------------
# configuration options related to source browsing
#---------------------------------------------------------------------------
@@ -697,7 +822,7 @@ FILTER_SOURCE_FILES = NO
# Note: To get rid of all source code in the generated output, make sure also
# VERBATIM_HEADERS is set to NO.
-SOURCE_BROWSER = NO
+SOURCE_BROWSER = YES
# Setting the INLINE_SOURCES tag to YES will include the body
# of functions and classes directly in the documentation.
@@ -706,7 +831,7 @@ INLINE_SOURCES = NO
# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct
# doxygen to hide any special comment blocks from generated source code
-# fragments. Normal C and C++ comments will always remain visible.
+# fragments. Normal C, C++ and Fortran comments will always remain visible.
STRIP_CODE_COMMENTS = YES
@@ -790,7 +915,14 @@ HTML_FILE_EXTENSION = .html
# The HTML_HEADER tag can be used to specify a personal HTML header for
# each generated HTML page. If it is left blank doxygen will generate a
-# standard header.
+# standard header. Note that when using a custom header you are responsible
+# for the proper inclusion of any scripts and style sheets that doxygen
+# needs, which is dependent on the configuration options used.
+# It is advised to generate a default header using "doxygen -w html
+# header.html footer.html stylesheet.css YourConfigFile" and then modify
+# that header. Note that the header is subject to change so you typically
+# have to redo this when upgrading to a newer version of doxygen or when
+# changing the value of configuration settings such as GENERATE_TREEVIEW!
HTML_HEADER =
@@ -802,26 +934,79 @@ HTML_FOOTER =
# The HTML_STYLESHEET tag can be used to specify a user-defined cascading
# style sheet that is used by each HTML page. It can be used to
-# fine-tune the look of the HTML output. If the tag is left blank doxygen
-# will generate a default style sheet. Note that doxygen will try to copy
-# the style sheet file to the HTML output directory, so don't put your own
-# stylesheet in the HTML output directory as well, or it will be erased!
+# fine-tune the look of the HTML output. If left blank doxygen will
+# generate a default style sheet. Note that it is recommended to use
+# HTML_EXTRA_STYLESHEET instead of this one, as it is more robust and this
+# tag will in the future become obsolete.
HTML_STYLESHEET =
-# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes,
-# files or namespaces will be aligned in HTML using tables. If set to
-# NO a bullet list will be used.
+# The HTML_EXTRA_STYLESHEET tag can be used to specify an additional
+# user-defined cascading style sheet that is included after the standard
+# style sheets created by doxygen. Using this option one can overrule
+# certain style aspects. This is preferred over using HTML_STYLESHEET
+# since it does not replace the standard style sheet and is therefor more
+# robust against future updates. Doxygen will copy the style sheet file to
+# the output directory.
+
+HTML_EXTRA_STYLESHEET =
+
+# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or
+# other source files which should be copied to the HTML output directory. Note
+# that these files will be copied to the base HTML output directory. Use the
+# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these
+# files. In the HTML_STYLESHEET file, use the file name only. Also note that
+# the files will be copied as-is; there are no commands or markers available.
+
+HTML_EXTRA_FILES =
+
+# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output.
+# Doxygen will adjust the colors in the style sheet and background images
+# according to this color. Hue is specified as an angle on a colorwheel,
+# see http://en.wikipedia.org/wiki/Hue for more information.
+# For instance the value 0 represents red, 60 is yellow, 120 is green,
+# 180 is cyan, 240 is blue, 300 purple, and 360 is red again.
+# The allowed range is 0 to 359.
+
+HTML_COLORSTYLE_HUE = 220
+
+# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of
+# the colors in the HTML output. For a value of 0 the output will use
+# grayscales only. A value of 255 will produce the most vivid colors.
+
+HTML_COLORSTYLE_SAT = 100
+
+# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to
+# the luminance component of the colors in the HTML output. Values below
+# 100 gradually make the output lighter, whereas values above 100 make
+# the output darker. The value divided by 100 is the actual gamma applied,
+# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2,
+# and 100 does not change the gamma.
+
+HTML_COLORSTYLE_GAMMA = 80
-HTML_ALIGN_MEMBERS = YES
+# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML
+# page will contain the date and time when the page was generated. Setting
+# this to NO can help when comparing the output of multiple runs.
+
+HTML_TIMESTAMP = YES
# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML
# documentation will contain sections that can be hidden and shown after the
-# page has loaded. For this to work a browser that supports
-# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox
-# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+# page has loaded.
+
+HTML_DYNAMIC_SECTIONS = YES
+
+# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of
+# entries shown in the various tree structured indices initially; the user
+# can expand and collapse entries dynamically later on. Doxygen will expand
+# the tree to such a level that at most the specified number of entries are
+# visible (unless a fully collapsed tree already exceeds this amount).
+# So setting the number of entries 1 will produce a full collapsed tree by
+# default. 0 is a special value representing an infinite number of entries
+# and will result in a full expanded tree by default.
-HTML_DYNAMIC_SECTIONS = NO
+HTML_INDEX_NUM_ENTRIES = 100
# If the GENERATE_DOCSET tag is set to YES, additional index files
# will be generated that can be used as input for Apple's Xcode 3
@@ -831,7 +1016,8 @@ HTML_DYNAMIC_SECTIONS = NO
# directory and running "make install" will install the docset in
# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find
# it at startup.
-# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information.
+# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html
+# for more information.
GENERATE_DOCSET = NO
@@ -849,6 +1035,16 @@ DOCSET_FEEDNAME = "Doxygen generated docs"
DOCSET_BUNDLE_ID = org.doxygen.Project
+# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely
+# identify the documentation publisher. This should be a reverse domain-name
+# style string, e.g. com.mycompany.MyDocSet.documentation.
+
+DOCSET_PUBLISHER_ID = org.doxygen.Publisher
+
+# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher.
+
+DOCSET_PUBLISHER_NAME = Publisher
+
# If the GENERATE_HTMLHELP tag is set to YES, additional index files
# will be generated that can be used as input for tools like the
# Microsoft HTML help workshop to generate a compiled HTML help file (.chm)
@@ -893,10 +1089,10 @@ BINARY_TOC = NO
TOC_EXPAND = NO
-# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER
-# are set, an additional index file will be generated that can be used as input for
-# Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated
-# HTML documentation.
+# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and
+# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated
+# that can be used as input for Qt's qhelpgenerator to generate a
+# Qt Compressed Help (.qch) of the generated HTML documentation.
GENERATE_QHP = NO
@@ -918,20 +1114,24 @@ QHP_NAMESPACE =
QHP_VIRTUAL_FOLDER = doc
-# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to add.
-# For more information please see
+# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to
+# add. For more information please see
# http://doc.trolltech.com/qthelpproject.html#custom-filters
QHP_CUST_FILTER_NAME =
-# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the custom filter to add.For more information please see
-# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">Qt Help Project / Custom Filters</a>.
+# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the
+# custom filter to add. For more information please see
+# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters">
+# Qt Help Project / Custom Filters</a>.
QHP_CUST_FILTER_ATTRS =
-# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this project's
+# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this
+# project's
# filter section matches.
-# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">Qt Help Project / Filter Attributes</a>.
+# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes">
+# Qt Help Project / Filter Attributes</a>.
QHP_SECT_FILTER_ATTRS =
@@ -942,16 +1142,30 @@ QHP_SECT_FILTER_ATTRS =
QHG_LOCATION =
-# The DISABLE_INDEX tag can be used to turn on/off the condensed index at
-# top of each HTML page. The value NO (the default) enables the index and
-# the value YES disables it.
+# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files
+# will be generated, which together with the HTML files, form an Eclipse help
+# plugin. To install this plugin and make it available under the help contents
+# menu in Eclipse, the contents of the directory containing the HTML and XML
+# files needs to be copied into the plugins directory of eclipse. The name of
+# the directory within the plugins directory should be the same as
+# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before
+# the help appears.
-DISABLE_INDEX = NO
+GENERATE_ECLIPSEHELP = NO
-# This tag can be used to set the number of enum values (range [1..20])
-# that doxygen will group on one line in the generated HTML documentation.
+# A unique identifier for the eclipse help plugin. When installing the plugin
+# the directory name containing the HTML and XML files should also have
+# this name.
-ENUM_VALUES_PER_LINE = 4
+ECLIPSE_DOC_ID = org.doxygen.Project
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs)
+# at top of each HTML page. The value NO (the default) enables the index and
+# the value YES disables it. Since the tabs have the same information as the
+# navigation tree you can set this option to NO if you already set
+# GENERATE_TREEVIEW to YES.
+
+DISABLE_INDEX = NO
# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index
# structure should be generated to display hierarchical information.
@@ -960,13 +1174,17 @@ ENUM_VALUES_PER_LINE = 4
# is generated for HTML Help). For this to work a browser that supports
# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser).
# Windows users are probably better off using the HTML help feature.
+# Since the tree basically has the same information as the tab index you
+# could consider to set DISABLE_INDEX to NO when enabling this option.
GENERATE_TREEVIEW = YES
-# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories,
-# and Class Hierarchy pages using a tree view instead of an ordered list.
+# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values
+# (range [0,1..20]) that doxygen will group on one line in the generated HTML
+# documentation. Note that a value of 0 will completely suppress the enum
+# values from appearing in the overview section.
-USE_INLINE_TREES = NO
+ENUM_VALUES_PER_LINE = 4
# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be
# used to set the initial width (in pixels) of the frame in which the tree
@@ -974,6 +1192,11 @@ USE_INLINE_TREES = NO
TREEVIEW_WIDTH = 180
+# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open
+# links to external symbols imported via tag files in a separate window.
+
+EXT_LINKS_IN_WINDOW = NO
+
# Use this tag to change the font size of Latex formulas included
# as images in the HTML documentation. The default is 10. Note that
# when you change the font size after a successful doxygen run you need
@@ -982,13 +1205,60 @@ TREEVIEW_WIDTH = 180
FORMULA_FONTSIZE = 10
-# When the SEARCHENGINE tag is enable doxygen will generate a search box for the HTML output. The underlying search engine uses javascript
-# and DHTML and should work on any modern browser. Note that when using HTML help (GENERATE_HTMLHELP) or Qt help (GENERATE_QHP)
-# there is already a search function so this one should typically
-# be disabled.
+# Use the FORMULA_TRANPARENT tag to determine whether or not the images
+# generated for formulas are transparent PNGs. Transparent PNGs are
+# not supported properly for IE 6.0, but are supported on all modern browsers.
+# Note that when changing this option you need to delete any form_*.png files
+# in the HTML output before the changes have effect.
+
+FORMULA_TRANSPARENT = YES
+
+# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax
+# (see http://www.mathjax.org) which uses client side Javascript for the
+# rendering instead of using prerendered bitmaps. Use this if you do not
+# have LaTeX installed or if you want to formulas look prettier in the HTML
+# output. When enabled you may also need to install MathJax separately and
+# configure the path to it using the MATHJAX_RELPATH option.
+
+USE_MATHJAX = NO
+
+# When MathJax is enabled you need to specify the location relative to the
+# HTML output directory using the MATHJAX_RELPATH option. The destination
+# directory should contain the MathJax.js script. For instance, if the mathjax
+# directory is located at the same level as the HTML output directory, then
+# MATHJAX_RELPATH should be ../mathjax. The default value points to
+# the MathJax Content Delivery Network so you can quickly see the result without
+# installing MathJax.
+# However, it is strongly recommended to install a local
+# copy of MathJax from http://www.mathjax.org before deployment.
+
+MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest
+
+# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension
+# names that should be enabled during MathJax rendering.
+
+MATHJAX_EXTENSIONS =
+
+# When the SEARCHENGINE tag is enabled doxygen will generate a search box
+# for the HTML output. The underlying search engine uses javascript
+# and DHTML and should work on any modern browser. Note that when using
+# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets
+# (GENERATE_DOCSET) there is already a search function so this one should
+# typically be disabled. For large projects the javascript based search engine
+# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution.
SEARCHENGINE = NO
+# When the SERVER_BASED_SEARCH tag is enabled the search engine will be
+# implemented using a PHP enabled web server instead of at the web client
+# using Javascript. Doxygen will generate the search PHP script and index
+# file to put on the web server. The advantage of the server
+# based approach is that it scales better to large projects and allows
+# full text search. The disadvantages are that it is more difficult to setup
+# and does not have live searching capabilities.
+
+SERVER_BASED_SEARCH = NO
+
#---------------------------------------------------------------------------
# configuration options related to the LaTeX output
#---------------------------------------------------------------------------
@@ -1006,6 +1276,9 @@ LATEX_OUTPUT = latex
# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be
# invoked. If left blank `latex' will be used as the default command name.
+# Note that when enabling USE_PDFLATEX this option is only used for
+# generating bitmaps for formulas in the HTML output, but not in the
+# Makefile that is written to the output directory.
LATEX_CMD_NAME = latex
@@ -1022,7 +1295,7 @@ MAKEINDEX_CMD_NAME = makeindex
COMPACT_LATEX = NO
# The PAPER_TYPE tag can be used to set the paper type that is used
-# by the printer. Possible values are: a4, a4wide, letter, legal and
+# by the printer. Possible values are: a4, letter, legal and
# executive. If left blank a4wide will be used.
PAPER_TYPE = a4wide
@@ -1039,6 +1312,13 @@ EXTRA_PACKAGES =
LATEX_HEADER =
+# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for
+# the generated latex document. The footer should contain everything after
+# the last chapter. If it is left blank doxygen will generate a
+# standard footer. Notice: only use this tag if you know what you are doing!
+
+LATEX_FOOTER =
+
# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated
# is prepared for conversion to pdf (using ps2pdf). The pdf file will
# contain links (just like the HTML output) instead of page references
@@ -1065,10 +1345,19 @@ LATEX_BATCHMODE = NO
LATEX_HIDE_INDICES = NO
-# If LATEX_SOURCE_CODE is set to YES then doxygen will include source code with syntax highlighting in the LaTeX output. Note that which sources are shown also depends on other settings such as SOURCE_BROWSER.
+# If LATEX_SOURCE_CODE is set to YES then doxygen will include
+# source code with syntax highlighting in the LaTeX output.
+# Note that which sources are shown also depends on other settings
+# such as SOURCE_BROWSER.
LATEX_SOURCE_CODE = NO
+# The LATEX_BIB_STYLE tag can be used to specify the style to use for the
+# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See
+# http://en.wikipedia.org/wiki/BibTeX for more info.
+
+LATEX_BIB_STYLE = plain
+
#---------------------------------------------------------------------------
# configuration options related to the RTF output
#---------------------------------------------------------------------------
@@ -1100,7 +1389,7 @@ COMPACT_RTF = NO
RTF_HYPERLINKS = NO
-# Load stylesheet definitions from file. Syntax is similar to doxygen's
+# Load style sheet definitions from file. Syntax is similar to doxygen's
# config file, i.e. a series of assignments. You only have to provide
# replacements, missing definitions are set to their default value.
@@ -1245,7 +1534,7 @@ MACRO_EXPANSION = YES
EXPAND_ONLY_PREDEF = NO
# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files
-# in the INCLUDE_PATH (see below) will be search if a #include is found.
+# pointed to by INCLUDE_PATH will be searched when a #include is found.
SEARCH_INCLUDES = YES
@@ -1275,15 +1564,15 @@ PREDEFINED =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then
# this tag can be used to specify a list of macro names that should be expanded.
# The macro definition that is found in the sources will be used.
-# Use the PREDEFINED tag if you want to use a different macro definition.
+# Use the PREDEFINED tag if you want to use a different macro definition that
+# overrules the definition found in the source code.
EXPAND_AS_DEFINED =
# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then
-# doxygen's preprocessor will remove all function-like macros that are alone
-# on a line, have an all uppercase name, and do not end with a semicolon. Such
-# function macros are typically used for boiler-plate code, and will confuse
-# the parser if not removed.
+# doxygen's preprocessor will remove all references to function-like macros
+# that are alone on a line, have an all uppercase name, and do not end with a
+# semicolon, because these will confuse the parser if not removed.
SKIP_FUNCTION_MACROS = YES
@@ -1291,22 +1580,18 @@ SKIP_FUNCTION_MACROS = YES
# Configuration::additions related to external references
#---------------------------------------------------------------------------
-# The TAGFILES option can be used to specify one or more tagfiles.
-# Optionally an initial location of the external documentation
-# can be added for each tagfile. The format of a tag file without
-# this location is as follows:
+# The TAGFILES option can be used to specify one or more tagfiles. For each
+# tag file the location of the external documentation should be added. The
+# format of a tag file without this location is as follows:
#
# TAGFILES = file1 file2 ...
# Adding location for the tag files is done as follows:
#
# TAGFILES = file1=loc1 "file2 = loc2" ...
-# where "loc1" and "loc2" can be relative or absolute paths or
-# URLs. If a location is present for each tag, the installdox tool
-# does not have to be run to correct the links.
-# Note that each tag file must have a unique name
-# (where the name does NOT include the path)
-# If a tag file is not located in the directory in which doxygen
-# is run, you must also specify the path to the tagfile here.
+# where "loc1" and "loc2" can be relative or absolute paths
+# or URLs. Note that each tag file must have a unique name (where the name does
+# NOT include the path). If a tag file is not located in the directory in which
+# doxygen is run, you must also specify the path to the tagfile here.
TAGFILES =
@@ -1339,9 +1624,8 @@ PERL_PATH = /usr/bin/perl
# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will
# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base
# or super classes. Setting the tag to NO turns the diagrams off. Note that
-# this option is superseded by the HAVE_DOT option below. This is only a
-# fallback. It is recommended to install and use dot, since it yields more
-# powerful graphs.
+# this option also works with HAVE_DOT disabled, but it is recommended to
+# install and use dot, since it yields more powerful graphs.
CLASS_DIAGRAMS = YES
@@ -1367,14 +1651,20 @@ HIDE_UNDOC_RELATIONS = YES
HAVE_DOT = NO
-# By default doxygen will write a font called FreeSans.ttf to the output
-# directory and reference it in all dot files that doxygen generates. This
-# font does not include all possible unicode characters however, so when you need
-# these (or just want a differently looking font) you can specify the font name
-# using DOT_FONTNAME. You need need to make sure dot is able to find the font,
-# which can be done by putting it in a standard location or by setting the
-# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory
-# containing the font.
+# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is
+# allowed to run in parallel. When set to 0 (the default) doxygen will
+# base this on the number of processors available in the system. You can set it
+# explicitly to a value larger than 0 to get control over the balance
+# between CPU load and processing speed.
+
+DOT_NUM_THREADS = 0
+
+# By default doxygen will use the Helvetica font for all dot files that
+# doxygen generates. When you want a differently looking font you can specify
+# the font name using DOT_FONTNAME. You need to make sure dot is able to find
+# the font, which can be done by putting it in a standard location or by setting
+# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the
+# directory containing the font.
DOT_FONTNAME = FreeSans
@@ -1383,17 +1673,16 @@ DOT_FONTNAME = FreeSans
DOT_FONTSIZE = 10
-# By default doxygen will tell dot to use the output directory to look for the
-# FreeSans.ttf font (which doxygen will put there itself). If you specify a
-# different font using DOT_FONTNAME you can set the path where dot
-# can find it using this tag.
+# By default doxygen will tell dot to use the Helvetica font.
+# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to
+# set the path where dot can find it.
DOT_FONTPATH =
# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen
# will generate a graph for each documented class showing the direct and
# indirect inheritance relations. Setting this tag to YES will force the
-# the CLASS_DIAGRAMS tag to NO.
+# CLASS_DIAGRAMS tag to NO.
CLASS_GRAPH = YES
@@ -1415,6 +1704,15 @@ GROUP_GRAPHS = YES
UML_LOOK = NO
+# If the UML_LOOK tag is enabled, the fields and methods are shown inside
+# the class node. If there are many fields or methods and many nodes the
+# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS
+# threshold limits the number of items for each type to make the size more
+# managable. Set this to 0 for no limit. Note that the threshold may be
+# exceeded by 50% before the limit is enforced.
+
+UML_LIMIT_NUM_FIELDS = 10
+
# If set to YES, the inheritance and collaboration graphs will show the
# relations between templates and their instances.
@@ -1451,11 +1749,11 @@ CALL_GRAPH = NO
CALLER_GRAPH = NO
# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen
-# will graphical hierarchy of all classes instead of a textual one.
+# will generate a graphical hierarchy of all classes instead of a textual one.
GRAPHICAL_HIERARCHY = YES
-# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES
+# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES
# then doxygen will show the dependencies a directory has on other directories
# in a graphical way. The dependency relations are determined by the #include
# relations between the files in the directories.
@@ -1463,11 +1761,22 @@ GRAPHICAL_HIERARCHY = YES
DIRECTORY_GRAPH = YES
# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images
-# generated by dot. Possible values are png, jpg, or gif
-# If left blank png will be used.
+# generated by dot. Possible values are svg, png, jpg, or gif.
+# If left blank png will be used. If you choose svg you need to set
+# HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible in IE 9+ (other browsers do not have this requirement).
DOT_IMAGE_FORMAT = png
+# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to
+# enable generation of interactive SVG images that allow zooming and panning.
+# Note that this requires a modern browser other than Internet Explorer.
+# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you
+# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files
+# visible. Older versions of IE do not have SVG support.
+
+INTERACTIVE_SVG = NO
+
# The tag DOT_PATH can be used to specify the path where the dot tool can be
# found. If left blank, it is assumed the dot tool can be found in the path.
@@ -1479,6 +1788,12 @@ DOT_PATH =
DOTFILE_DIRS =
+# The MSCFILE_DIRS tag can be used to specify one or more directories that
+# contain msc files that are included in the documentation (see the
+# \mscfile command).
+
+MSCFILE_DIRS =
+
# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of
# nodes that will be shown in the graph. If the number of nodes in a graph
# becomes larger than this value, doxygen will truncate the graph, which is
diff --git a/doc/devel/02-dhcp.dox b/doc/devel/02-dhcp.dox
index 5c59daa..b98920f 100644
--- a/doc/devel/02-dhcp.dox
+++ b/doc/devel/02-dhcp.dox
@@ -19,7 +19,7 @@
*
* @section dhcpv4Session BIND10 message queue integration
*
- * DHCPv4 server component is now integrated with BIND10 message queue.
+ * DHCPv4 server component is now integrated with the BIND10 message queue.
* The integration is performed by establishSession() and disconnectSession()
* functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
* in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
@@ -57,4 +57,4 @@
* that does not support msgq. That is useful for embedded environments.
* It may also be useful in validation.
*
- */
\ No newline at end of file
+ */
diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox
index db42c14..295fd03 100644
--- a/doc/devel/mainpage.dox
+++ b/doc/devel/mainpage.dox
@@ -20,12 +20,14 @@
* - @subpage DataScrubbing
*
* @section DHCP
- * - @subpage dhcpv4
+ * - @subpage dhcp4
* - @subpage dhcpv4Session
- * - @subpage dhcpv6
- * - @subpage dhcpv6-session
- * - @subpage dhcpv6-config-parser
- * - @subpage dhcpv6-config-inherit
+ * - @subpage dhcpv4ConfigParser
+ * - @subpage dhcpv4ConfigInherit
+ * - @subpage dhcp6
+ * - @subpage dhcpv6Session
+ * - @subpage dhcpv6ConfigParser
+ * - @subpage dhcpv6ConfigInherit
* - @subpage libdhcp
* - @subpage libdhcpIntro
* - @subpage libdhcpIfaceMgr
@@ -33,7 +35,7 @@
* - @subpage leasemgr
* - @subpage cfgmgr
* - @subpage allocengine
- * - @subpage dhcp-database-backends
+ * - @subpage dhcpDatabaseBackends
* - @subpage perfdhcpInternals
*
* @section misc Miscellaneous topics
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index d585b63..f1f5859 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -425,11 +425,12 @@ var/
</listitem>
<listitem>
- <para>In another console, enable the authoritative DNS service
- (by using the <command>bindctl</command> utility to configure
- the <command>b10-auth</command> component to run):
- <screen>$ <userinput>bindctl</userinput></screen>
- (Login with the provided default username and password.)
+ <para>DNS and DHCP components are not started in the default
+ configuration. In another console, enable the authoritative
+ DNS service (by using the <command>bindctl</command> utility
+ to configure the <command>b10-auth</command> component to
+ run): <screen>$ <userinput>bindctl</userinput></screen>
+ (Login with the provided default username and password.)
<screen>
> <userinput>config add Boss/components b10-auth</userinput>
> <userinput>config set Boss/components/b10-auth/special auth</userinput>
@@ -448,8 +449,10 @@ var/
<listitem>
<para>Load desired zone file(s), for example:
- <screen>$ <userinput>b10-loadzone <replaceable>your.zone.example.org</replaceable></userinput></screen>
+ <screen>$ <userinput>b10-loadzone <replaceable>-c '{"database_file": "/usr/local/var/bind10/zone.sqlite3"}'</replaceable> <replaceable>your.zone.example.org</replaceable> <replaceable>your.zone.file</replaceable></userinput></screen>
</para>
+ (If you use the sqlite3 data source with the default DB
+ file, you can omit the -c option).
</listitem>
<listitem>
@@ -471,7 +474,7 @@ var/
<title>Packages</title>
<para>
- Some operating systems or softare package vendors may
+ Some operating systems or software package vendors may
provide ready-to-use, pre-built software packages for
the BIND 10 suite.
Installing a pre-built package means you do not need to
@@ -500,7 +503,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>etc/bind10-devel/</filename> —
+ <filename>etc/bind10/</filename> —
configuration files.
</simpara>
</listitem>
@@ -512,7 +515,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>libexec/bind10-devel/</filename> —
+ <filename>libexec/bind10/</filename> —
executables that a user wouldn't normally run directly and
are not run independently.
These are the BIND 10 modules which are daemons started by
@@ -527,13 +530,13 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>share/bind10-devel/</filename> —
+ <filename>share/bind10/</filename> —
configuration specifications.
</simpara>
</listitem>
<listitem>
<simpara>
- <filename>share/doc/bind10-devel/</filename> —
+ <filename>share/doc/bind10/</filename> —
this guide and other supplementary documentation.
</simpara>
</listitem>
@@ -545,7 +548,7 @@ var/
</listitem>
<listitem>
<simpara>
- <filename>var/bind10-devel/</filename> —
+ <filename>var/bind10/</filename> —
data source and configuration databases.
</simpara>
</listitem>
@@ -757,7 +760,7 @@ as a dependency earlier -->
If the configure fails, it may be due to missing or old
dependencies.
</para>
-
+
<note>
<para>For notes on configuring and building DHCPv6 with MySQL see <xref linkend="dhcp6-install">.</xref></para>
</note>
@@ -783,7 +786,24 @@ as a dependency earlier -->
<note>
<para>The install step may require superuser privileges.</para>
</note>
-
+ <para>
+ If required, run <command>ldconfig</command> as root with
+ <filename>/usr/local/lib</filename> (or with ${prefix}/lib if
+ configured with --prefix) in
+ <filename>/etc/ld.so.conf</filename> (or the relevant linker
+ cache configuration file for your OS):
+ <screen>$ <userinput>ldconfig</userinput></screen>
+ </para>
+ <note>
+ <para>
+ If you do not run <command>ldconfig</command> where it is
+ required, you may see errors like the following:
+ <screen>
+ program: error while loading shared libraries: libb10-something.so.1:
+ cannot open shared object file: No such file or directory
+ </screen>
+ </para>
+ </note>
</section>
<!-- TODO: tests -->
@@ -890,7 +910,7 @@ as a dependency earlier -->
Administrators do not communicate directly with the
<command>b10-msgq</command> daemon.
By default, BIND 10 uses a UNIX domain socket file named
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>
+ <filename>/usr/local/var/bind10/msg_socket</filename>
for this interprocess communication.
</para>
@@ -952,7 +972,7 @@ config changes are actually commands to cfgmgr
<!-- TODO: what about command line switch to change this? -->
<para>
The stored configuration file is at
- <filename>/usr/local/var/bind10-devel/b10-config.db</filename>.
+ <filename>/usr/local/var/bind10/b10-config.db</filename>.
(The directory is what was defined at build configure time for
<option>--localstatedir</option>.
The default is <filename>/usr/local/var/</filename>.)
@@ -1045,13 +1065,13 @@ but you might wanna check with likun
<para>The HTTPS server requires a private key,
such as a RSA PRIVATE KEY.
The default location is at
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
(A sample key is at
- <filename>/usr/local/share/bind10-devel/cmdctl-keyfile.pem</filename>.)
+ <filename>/usr/local/share/bind10/cmdctl-keyfile.pem</filename>.)
It also uses a certificate located at
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
(A sample certificate is at
- <filename>/usr/local/share/bind10-devel/cmdctl-certfile.pem</filename>.)
+ <filename>/usr/local/share/bind10/cmdctl-certfile.pem</filename>.)
This may be a self-signed certificate or purchased from a
certification authority.
</para>
@@ -1087,11 +1107,11 @@ but that is a single file, maybe this should go back to that format?
<para>
The <command>b10-cmdctl</command> daemon also requires
the user account file located at
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
This comma-delimited file lists the accounts with a user name,
hashed password, and salt.
(A sample file is at
- <filename>/usr/local/share/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/share/bind10/cmdctl-accounts.csv</filename>.
It contains the user named <quote>root</quote> with the password
<quote>bind10</quote>.)
</para>
@@ -1121,14 +1141,14 @@ or accounts database -->
The configuration items for <command>b10-cmdctl</command> are:
<varname>accounts_file</varname> which defines the path to the
user accounts database (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>);
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>);
<varname>cert_file</varname> which defines the path to the
PEM certificate file (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>);
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>);
and
<varname>key_file</varname> which defines the path to the
PEM private key file (the default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>).
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>).
</para>
</section>
@@ -1823,10 +1843,8 @@ config set /Boss/components/b10-zonemgr/kind dispensable
<para>
The key ring lives in the configuration in "tsig_keys/keys". Most of
the system uses the keys from there — ACLs, authoritative server to
- sign responses to signed queries, and <command>b10-xfrout</command>
- to sign transfers. The <command>b10-xfrin</command> uses its own
- configuration for keys, but that will be fixed in Trac ticket
- <ulink url="http://bind10.isc.org/ticket/1351">#1351</ulink>.
+ sign responses to signed queries, and <command>b10-xfrin</command>
+ and <command>b10-xfrout</command> to sign transfers.
</para>
<para>
@@ -1854,7 +1872,7 @@ tsig_keys/keys[0] "example.key.:c2VjcmV0" string (modified)
Each rule is a name-value mapping (a dictionary, in the JSON
terminology). Each rule must contain exactly one mapping called
"action", which describes what should happen if the rule applies.
- There may be more mappings, calld matches, which describe the
+ There may be more mappings, called matches, which describe the
conditions under which the rule applies.
</para>
@@ -2139,7 +2157,7 @@ AND_MATCH := "ALL": [ RULE_RAW, RULE_RAW, ... ]
you indicate that the system is not usable without the
component and if such component fails, the system shuts
down no matter when the failure happened. This is the
- behaviour of the core components (the ones you can't turn
+ behavior of the core components (the ones you can't turn
off), but you can declare any other components as core as
well if you wish (but you can turn these off, they just
can't fail).
@@ -2441,7 +2459,7 @@ can use various data source backends.
data source — one that serves things like
<quote>AUTHORS.BIND.</quote>. The IN class contains single SQLite3
data source with database file located at
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
</para>
<para>
@@ -2620,19 +2638,10 @@ can use various data source backends.
</para>
- <para>
- The <option>-o</option> argument may be used to define the
- default origin for loaded zone file records.
- </para>
-
<note>
<para>
In the current release, only the SQLite3 back
end is used by <command>b10-loadzone</command>.
- By default, it stores the zone data in
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
- unless the <option>-d</option> switch is used to set the
- database filename.
Multiple zones are stored in a single SQLite3 zone database.
</para>
</note>
@@ -2704,6 +2713,15 @@ TODO
</section>
<section>
+ <title>TSIG</title>
+ If you want to use TSIG for incoming transfers, a system wide TSIG
+ key ring must be configured (see <xref linkend="tsig-key-ring"/>).
+ To specify a key to use, set tsig_key value to the name of the key
+ to use from the key ring.
+> <userinput>config set Xfrin/zones[0]/tsig_key "<option>example.key</option>"</userinput>
+ </section>
+
+ <section>
<title>Enabling IXFR</title>
<para>
As noted above, <command>b10-xfrin</command> uses AXFR for
@@ -3325,23 +3343,21 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
<note>
<para>
- As of November 2012, the DHCPv4 component is a
- skeleton server. That means that while it is capable of
- performing DHCP configuration, it is not fully functional.
- In particular, it does not have a functional lease
- database. This means that they will assign the same, fixed,
- hardcoded addresses to any client that will ask. See <xref
- linkend="dhcp4-limit"/> for a
- detailed description.
+ As of January 2013, the DHCPv4 component is a work in progress.
+ That means that while it is capable of performing DHCP configuration,
+ it is not fully functional. The server is able to offer,
+ assign, renew, release and reuse expired leases, but some of the
+ options are not configurable yet. In particular Router option is hardcoded.
+ This means that the server is not really usable in actual deployments
+ yet. See <xref linkend="dhcp4-limit"/> for a detailed description.
</para>
</note>
<section id="dhcp4-usage">
<title>DHCPv4 Server Usage</title>
<para>BIND 10 has provided the DHCPv4 server component since December
- 2011. It is a skeleton server and can be described as an early
- prototype that is not fully functional yet. It is mature enough
- to conduct first tests in lab environment, but it has
+ 2011. It is current experimental implementation and is not fully functional
+ yet. It is mature enough to conduct tests in lab environment, but it has
significant limitations. See <xref linkend="dhcp4-limit"/> for
details.
</para>
@@ -3379,16 +3395,95 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
<section id="dhcp4-config">
<title>DHCPv4 Server Configuration</title>
<para>
- The DHCPv4 server does not have a lease database implemented yet
- nor any support for configuration, so the same set
- of configuration options (including the same fixed address)
- will be assigned every time.
+ Once the server is started, it can be configured. To view the
+ current configuration, use the following command in <command>bindctl</command>:
+ <screen>
+> <userinput>config show Dhcp4</userinput></screen>
+ When starting Dhcp4 daemon for the first time, the default configuration
+ will be available. It will look similar to this:
+ <screen>
+> <userinput>config show Dhcp4</userinput>
+Dhcp4/interface/ list (default)
+Dhcp4/renew-timer 1000 integer (default)
+Dhcp4/rebind-timer 2000 integer (default)
+Dhcp4/preferred-lifetime 3000 integer (default)
+Dhcp4/valid-lifetime 4000 integer (default)
+Dhcp4/subnet4 [] list (default)</screen>
+ </para>
+
+ <para>
+ To change one of the parameters, simply follow
+ the usual <command>bindctl</command> procedure. For example, to make the
+ leases longer, change their valid-lifetime parameter:
+ <screen>
+> <userinput>config set Dhcp4/valid-lifetime 7200</userinput>
+> <userinput>config commit</userinput></screen>
+ Please note that most Dhcp4 parameters are of global scope
+ and apply to all defined subnets, unless they are overridden on a
+ per-subnet basis.
</para>
+
<para>
- At this stage of development, the only way to alter the server
- configuration is to modify its source code. To do so, please
- edit src/bin/dhcp4/dhcp4_srv.cc file and modify following
- parameters and recompile:
+ The essential role of DHCPv4 server is address assignment. The server
+ has to be configured with at least one subnet and one pool of dynamic
+ addresses to be managed. For example, assume that the server
+ is connected to a network segment that uses the 192.0.2.0/24
+ prefix. The Administrator of that network has decided that addresses from range
+ 192.0.2.10 to 192.0.2.20 are going to be managed by the Dhcp4
+ server. Such a configuration can be achieved in the following way:
+ <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/subnet "192.0.2.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10 - 192.0.2.20" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ Note that subnet is defined as a simple string, but the pool parameter
+ is actually a list of pools: for this reason, the pool definition is
+ enclosed in square brackets, even though only one range of addresses
+ is specified.</para>
+ <para>It is possible to define more than one pool in a
+ subnet: continuing the previous example, further assume that
+ 192.0.2.64/26 should be also be managed by the server. It could be written as
+ 192.0.2.64 to 192.0.2.127. Alternatively, it can be expressed more simply as
+ 192.0.2.64/26. Both formats are supported by Dhcp4 and can be mixed in the pool list.
+ For example, one could define the following pools:
+ <screen>
+> <userinput>config set Dhcp4/subnet4[0]/pool [ "192.0.2.10-192.0.2.20", "192.0.2.64/26" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ The number of pools is not limited, but for performance reasons it is recommended to
+ use as few as possible. Space and tabulations in pool definitions are ignored, so
+ spaces before and after hyphen are optional. They can be used to improve readability.
+ </para>
+ <para>
+ The server may be configured to serve more than one subnet. To add a second subnet,
+ use a command similar to the following:
+ <screen>
+> <userinput>config add Dhcp4/subnet4</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/subnet "192.0.3.0/24"</userinput>
+> <userinput>config set Dhcp4/subnet4[1]/pool [ "192.0.3.0/24" ]</userinput>
+> <userinput>config commit</userinput></screen>
+ Arrays are counted from 0. subnet[0] refers to the subnet defined in the
+ previous example. The <command>config add Dhcp4/subnet4</command> adds
+ another (second) subnet. It can be referred to as
+ <command>Dhcp4/subnet4[1]</command>. In this example, we allow server to
+ dynamically assign all addresses available in the whole subnet.
+ </para>
+ <para>
+ When configuring a DHCPv4 server using prefix/length notation, please pay
+ attention to the boundary values. When specifying that the server should use
+ a given pool, it will be able to allocate also first (typically network
+ address) and the last (typically broadcast address) address from that pool.
+ In the aforementioned example of pool 192.0.3.0/24, both 192.0.3.0 and
+ 192.0.3.255 addresses may be assigned as well. This may be invalid in some
+ network configurations. If you want to avoid this, please use min-max notation.
+ </para>
+
+ <para>
+ Note: Although configuration is now accepted, some parts of it is not internally used
+ by they server yet. Address pools are used, but option definitons are not.
+ The only way to alter some options (e.g. Router Option or DNS servers and Domain name)
+ is to modify source code. To do so, please edit
+ src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
+ recompile:
<screen>
const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
const std::string HARDCODED_NETMASK = "255.255.255.0";
@@ -3398,7 +3493,7 @@ const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
- Lease database and configuration support is planned for 2012.
+ Lease database and configuration support is planned for end of 2012.
</para>
</section>
@@ -3409,7 +3504,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
<itemizedlist>
<listitem>
<simpara>RFC2131: Supported messages are DISCOVER, OFFER,
- REQUEST, and ACK.</simpara>
+ REQUEST, ACK, NAK, RELEASE.</simpara>
</listitem>
<listitem>
<simpara>RFC2132: Supported options are: PAD (0),
@@ -3417,6 +3512,10 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
Domain Name (15), DNS Servers (6), IP Address Lease Time
(51), Subnet mask (1), and Routers (3).</simpara>
</listitem>
+ <listitem>
+ <simpara>RFC6842: Server responses include client-id option
+ if client sent it in its message.</simpara>
+ </listitem>
</itemizedlist>
</section>
@@ -3437,20 +3536,6 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
communication).</simpara>
</listitem>
<listitem>
- <simpara><command>b10-dhcp4</command> provides a single,
- fixed, hardcoded lease to any client that asks. There is
- no lease manager implemented. If two clients request
- addresses, they will both get the same fixed
- address.</simpara>
- </listitem>
- <listitem>
- <simpara><command>b10-dhcp4</command> does not support any
- configuration mechanisms yet. The whole configuration is
- currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see <xref
- linkend="dhcp4-config"/> for details.</simpara>
- </listitem>
- <listitem>
<simpara>Upon start, the server will open sockets on all
interfaces that are not loopback, are up and running and
have IPv4 address.</simpara>
@@ -3478,9 +3563,9 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
sending ICMP echo request.</simpara>
</listitem>
<listitem>
- <simpara>Address renewal (RENEW), rebinding (REBIND),
- confirmation (CONFIRM), duplication report (DECLINE) and
- release (RELEASE) are not supported yet.</simpara>
+ <simpara>Address rebinding (REQUEST/Rebinding), confirmation
+ (CONFIRM) and duplication report (DECLINE) are not supported
+ yet.</simpara>
</listitem>
<listitem>
<simpara>DNS Update is not supported yet.</simpara>
@@ -3577,7 +3662,7 @@ mysql></screen>
<para>
3. Create the database tables:
<screen>mysql> <userinput>CONNECT kea;</userinput>
-mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10-devel/dhcpdb_create.mysql</userinput></screen>
+mysql> <userinput>SOURCE <replaceable><path-to-bind10></replaceable>/share/bind10/dhcpdb_create.mysql</userinput></screen>
</para>
<para>
4. Create the user under which BIND 10 will access the database and grant it access to the database tables:
@@ -3775,7 +3860,7 @@ Dhcp6/subnet6 [] list (default)</screen>
<section id="dhcp6-limit">
<title>DHCPv6 Server Limitations</title>
<para> These are the current limitations and known problems
- with the the DHCPv6 server
+ with the DHCPv6 server
software. Most of them are reflections of the early stage of
development and should be treated as <quote>not implemented
yet</quote>, rather than actual limitations.</para>
@@ -4060,7 +4145,7 @@ specify module-wide logging and see what appears...
If there are multiple logger specifications in the
configuration that might match a particular logger, the
specification with the more specific logger name takes
- precedence. For example, if there are entries for for
+ precedence. For example, if there are entries for
both <quote>*</quote> and <quote>Resolver</quote>, the
resolver module — and all libraries it uses —
will log messages according to the configuration in the
diff --git a/examples/README b/examples/README
index 08f53fa..aa5f3c9 100644
--- a/examples/README
+++ b/examples/README
@@ -15,7 +15,7 @@ the "m4" subdirectory as a template for your own project. The key is
to call the AX_ISC_BIND10 function (as the sample configure.ac does)
from your configure.ac. Then it will check the availability of
necessary stuff and set some corresponding AC variables. You can then
-use the resulting variables in your Makefile.in or Makefile.ac.
+use the resulting variables in your Makefile.in or Makefile.am.
If you use automake, don't forget adding the following line to the top
level Makefile.am:
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
new file mode 100644
index 0000000..1ce367e
--- /dev/null
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -0,0 +1,112 @@
+dnl @synopsis AX_BOOST_FOR_BIND10
+dnl
+dnl Test for the Boost C++ header files intended to be used within BIND 10
+dnl
+dnl If no path to the installed boost header files is given via the
+dnl --with-boost-include option, the macro searchs under
+dnl /usr/local /usr/pkg /opt /opt/local directories.
+dnl If it cannot detect any workable path for Boost, this macro treats it
+dnl as a fatal error (so it cannot be called if the availability of Boost
+dnl is optional).
+dnl
+dnl This macro also tries to identify some known portability issues, and
+dnl sets corresponding variables so the caller can react to (or ignore,
+dnl depending on other configuration) specific issues appropriately.
+dnl
+dnl This macro calls:
+dnl
+dnl AC_SUBST(BOOST_INCLUDES)
+dnl
+dnl And possibly sets:
+dnl CPPFLAGS_BOOST_THREADCONF should be added to CPPFLAGS by caller
+dnl BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
+dnl error; otherwise set to "no"
+dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
+dnl build error; otherwise set to "no"
+dnl
+
+AC_DEFUN([AX_BOOST_FOR_BIND10], [
+AC_LANG_SAVE
+AC_LANG([C++])
+
+#
+# Configure Boost header path
+#
+# If explicitly specified, use it.
+AC_ARG_WITH([boost-include],
+ AC_HELP_STRING([--with-boost-include=PATH],
+ [specify exact directory for Boost headers]),
+ [boost_include_path="$withval"])
+# If not specified, try some common paths.
+if test -z "$with_boost_include"; then
+ boostdirs="/usr/local /usr/pkg /opt /opt/local"
+ for d in $boostdirs
+ do
+ if test -f $d/include/boost/shared_ptr.hpp; then
+ boost_include_path=$d/include
+ break
+ fi
+ done
+fi
+
+# Check the path with some specific headers.
+CPPFLAGS_SAVED="$CPPFLAGS"
+if test "${boost_include_path}" ; then
+ BOOST_INCLUDES="-I${boost_include_path}"
+ CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+fi
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
+ AC_MSG_ERROR([Missing required header files.]))
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly. In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+# Boost offset_ptr is known to not compile on some platforms, depending on
+# boost version, its local configuration, and compiler. Detect it.
+AC_MSG_CHECKING([Boost offset_ptr compiles])
+AC_TRY_COMPILE([
+#include <boost/interprocess/offset_ptr.hpp>
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_OFFSET_PTR_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_OFFSET_PTR_WOULDFAIL=yes])
+
+# Detect build failure case known to happen with Boost installed via
+# FreeBSD ports
+if test "X$GXX" = "Xyes"; then
+ CXXFLAGS_SAVED="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -Werror"
+
+ AC_MSG_CHECKING([Boost numeric_cast compiles with -Werror])
+ AC_TRY_COMPILE([
+ #include <boost/numeric/conversion/cast.hpp>
+ ],[
+ return (boost::numeric_cast<short>(0));
+ ],[AC_MSG_RESULT(yes)
+ BOOST_NUMERIC_CAST_WOULDFAIL=no],
+ [AC_MSG_RESULT(no)
+ BOOST_NUMERIC_CAST_WOULDFAIL=yes])
+
+ CXXFLAGS="$CXXFLAGS_SAVED"
+else
+ # This doesn't matter for non-g++
+ BOOST_NUMERIC_CAST_WOULDFAIL=no
+fi
+
+AC_SUBST(BOOST_INCLUDES)
+
+CPPFLAGS="$CPPFLAGS_SAVED"
+AC_LANG_RESTORE
+])dnl AX_BOOST_FOR_BIND10
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index ac9ffb9..d93da51 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -141,7 +141,7 @@ The specific problem is printed in the log message.
The thread for maintaining data source clients has received a command to
reconfigure, and has now started this process.
-% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed successfully
The thread for maintaining data source clients has finished reconfiguring
the data source clients, and is now running with the new configuration.
@@ -169,7 +169,7 @@ probably better to stop and restart it.
% AUTH_DATA_SOURCE data source database file: %1
This is a debug message produced by the authoritative server when it accesses a
-datebase data source, listing the file that is being accessed.
+database data source, listing the file that is being accessed.
% AUTH_DNS_SERVICES_CREATED DNS services created
This is a debug message indicating that the component that will handling
@@ -184,7 +184,7 @@ reason for the failure is given in the message.) The server will drop the
packet.
% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
+An error was encountered when the authoritative server specified
statistics data which is invalid for the auth specification file.
% AUTH_LOAD_TSIG loading TSIG keys
@@ -208,7 +208,7 @@ requests to b10-ddns) to handle it, but it failed. The authoritative
server returns SERVFAIL to the client on behalf of the separate
process. The error could be configuration mismatch between b10-auth
and the recipient component, or it may be because the requests are
-coming too fast and the receipient process cannot keep up with the
+coming too fast and the recipient process cannot keep up with the
rate, or some system level failure. In either case this means the
BIND 10 system is not working as expected, so the administrator should
look into the cause and address the issue. The log message includes
diff --git a/src/bin/auth/b10-auth.xml b/src/bin/auth/b10-auth.xml
index b34009d..88f80e2 100644
--- a/src/bin/auth/b10-auth.xml
+++ b/src/bin/auth/b10-auth.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>June 20, 2012</date>
+ <date>December 18, 2012</date>
</refentryinfo>
<refmeta>
@@ -100,7 +100,7 @@
<varname>database_file</varname> defines the path to the
SQLite3 zone file when using the sqlite datasource.
The default is
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>.
</para>
<para>
@@ -157,6 +157,7 @@
incoming TCP connections, in milliseconds. If the query
is not sent within this time, the connection is closed.
Setting this to 0 will disable TCP timeouts completely.
+ The default is 5000 (five seconds).
</para>
<!-- TODO: formating -->
@@ -165,6 +166,15 @@
</para>
<para>
+ <command>getstats</command> tells <command>b10-auth</command>
+ to report its defined statistics data in JSON format.
+ It will not report about unused counters.
+ This is used by the
+ <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry> daemon.
+ (The <command>sendstats</command> command is deprecated.)
+ </para>
+
+ <para>
<command>loadzone</command> tells <command>b10-auth</command>
to load or reload a zone file. The arguments include:
<varname>class</varname> which optionally defines the class
@@ -181,13 +191,6 @@
</para>
<para>
- <command>sendstats</command> tells <command>b10-auth</command>
- to send its statistics data to
- <citerefentry><refentrytitle>b10-stats</refentrytitle><manvolnum>8</manvolnum></citerefentry>
- immediately.
- </para>
-
- <para>
<command>shutdown</command> exits <command>b10-auth</command>.
This has an optional <varname>pid</varname> argument to
select the process ID to stop.
@@ -195,6 +198,28 @@
if configured.)
</para>
+ <para>
+ <command>start_ddns_forwarder</command> starts (or restarts) the
+ internal forwarding of DDNS Update messages.
+ This is used by the
+ <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon to tell <command>b10-auth</command> that DDNS Update
+ messages can be forwarded.
+ <note><simpara>This is not expected to be called by administrators;
+ it will be removed as a public command in the future.</simpara></note>
+ </para>
+
+ <para>
+ <command>stop_ddns_forwarder</command> stops the internal
+ forwarding of DDNS Update messages.
+ This is used by the
+ <citerefentry><refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ daemon to tell <command>b10-auth</command> that DDNS Update
+ messages should not be forwarded.
+ <note><simpara>This is not expected to be called by administrators;
+ it will be removed as a public command in the future.</simpara></note>
+ </para>
+
</refsect1>
<refsect1>
@@ -230,7 +255,7 @@
<refsect1>
<title>FILES</title>
<para>
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>
+ <filename>/usr/local/var/bind10/zone.sqlite3</filename>
— Location for the SQLite3 zone database
when <emphasis>database_file</emphasis> configuration is not
defined.
@@ -244,6 +269,9 @@
<refentrytitle>b10-cfgmgr</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
+ <refentrytitle>b10-ddns</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citerefentry>
<refentrytitle>b10-loadzone</refentrytitle><manvolnum>8</manvolnum>
</citerefentry>,
<citerefentry>
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index 1fe0f48..e90d199 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -147,7 +147,7 @@ main(int argc, char* argv[]) {
// Initialize logging. If verbose, we'll use maximum verbosity.
isc::log::initLogger(AUTH_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, true);
int ret = 0;
@@ -256,7 +256,9 @@ main(int argc, char* argv[]) {
// If we haven't registered callback for data sources, this will be just
// no-op.
- config_session->removeRemoteConfig("data_sources");
+ if (config_session != NULL) {
+ config_session->removeRemoteConfig("data_sources");
+ }
delete xfrin_session;
delete config_session;
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
index d6d1ec8..a45eff7 100644
--- a/src/bin/auth/tests/.gitignore
+++ b/src/bin/auth/tests/.gitignore
@@ -1 +1,3 @@
/run_unittests
+/example_base_inc.cc
+/example_nsec3_inc.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 42f202b..d1bde4d 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_builddir)/src/bin/cfgmgr/plugins\"
@@ -50,7 +51,6 @@ run_unittests_SOURCES += config_syntax_unittest.cc
run_unittests_SOURCES += command_unittest.cc
run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += query_inmemory_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
@@ -81,6 +81,39 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+# The following are definitions for auto-generating test data for query
+# tests.
+BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += testdata/example-base.sqlite3
+BUILT_SOURCES += testdata/example-nsec3.sqlite3
+
+EXTRA_DIST = gen-query-testdata.py
+
+CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
+
+example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-base-inc.zone example_base_inc.cc
+
+example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
+
+testdata/example-common-inc.zone: $(srcdir)/testdata/example-common-inc-template.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+
+testdata/example-base.sqlite3: testdata/example-base.zone testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
+ example.com testdata/example-base.zone
+
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
+ example.com testdata/example-nsec3.zone
+
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 8015043..826d1b8 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -225,7 +225,7 @@ createBuiltinVersionResponse(const qid_t qid, vector<uint8_t>& data) {
message.setHeaderFlag(Message::HEADERFLAG_AA);
RRsetPtr rrset_version = RRsetPtr(new RRset(version_name, RRClass::CH(),
RRType::TXT(), RRTTL(0)));
- rrset_version->addRdata(generic::TXT(PACKAGE_STRING));
+ rrset_version->addRdata(generic::TXT("\"" PACKAGE_STRING "\""));
message.addRRset(Message::SECTION_ANSWER, rrset_version);
RRsetPtr rrset_version_ns = RRsetPtr(new RRset(apex_name, RRClass::CH(),
@@ -1286,12 +1286,12 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
createRequestPacket(request_message, IPPROTO_UDP);
// Modify the message.
- delete io_message;
- endpoint = IOEndpoint::create(IPPROTO_UDP,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
- io_message = new IOMessage(request_renderer.getData(),
- request_renderer.getLength(),
- getDummyUnknownSocket(), *endpoint);
+ endpoint.reset(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress(DEFAULT_REMOTE_ADDRESS),
+ 53210));
+ io_message.reset(new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ getDummyUnknownSocket(), *endpoint));
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -1716,9 +1716,20 @@ void
checkAddrPort(const struct sockaddr& actual_sa,
const string& expected_addr, uint16_t expected_port)
{
+ // ASIO does not set as_len, which is not a problem on most
+ // systems, but it will make getnameinfo() fail on NetBSD 4
+ // So we make a copy and if the field is available, we set it
+ const socklen_t sa_len = getSALength(actual_sa);
+ struct sockaddr_storage ss;
+ memcpy(&ss, &actual_sa, sa_len);
+
+ struct sockaddr* sa = convertSockAddr(&ss);
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sa_len;
+#endif
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
- sizeof(hbuf), sbuf, sizeof(sbuf),
+ const int error = getnameinfo(sa, sa_len, hbuf, sizeof(hbuf),
+ sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (error != 0) {
isc_throw(isc::Unexpected, "getnameinfo failed: " <<
diff --git a/src/bin/auth/tests/gen-query-testdata.py b/src/bin/auth/tests/gen-query-testdata.py
new file mode 100755
index 0000000..a71deb0
--- /dev/null
+++ b/src/bin/auth/tests/gen-query-testdata.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This is a supplemental script to auto generate test data in the form of
+C++ source code from a DNS zone file.
+
+Usage: python gen-query-testdata.py source_file output-cc-file
+
+The usage doesn't matter much, though, because it's expected to be invoked
+from Makefile, and that would be only use case of this script.
+"""
+
+import sys
+import re
+
+# Markup for variable definition
+re_start_rr = re.compile('^;var=(.*)')
+
+# Skip lines starting with ';' (comments) or empty lines. re_start_rr
+# will also match this expression, so it should be checked first.
+re_skip = re.compile('(^;)|(^\s*$)')
+
+def parse_input(input_file):
+ '''Build an internal list of RR data from the input source file.
+
+ It generates a list of (variable_name, list of RR) tuples, where
+ variable_name is the expected C++ variable name for the subsequent RRs
+ if they are expected to be named. It can be an empty string if the RRs
+ are only expected to appear in the zone file.
+ The second element of the tuple is a list of strings, each of which
+ represents a single RR, e.g., "example.com 3600 IN A 192.0.2.1".
+
+ '''
+ result = []
+ rrs = None
+ with open(input_file) as f:
+ for line in f:
+ m = re_start_rr.match(line)
+ if m:
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+ rrs = []
+ rr_varname = m.group(1)
+ elif re_skip.match(line):
+ continue
+ else:
+ rrs.append(line.rstrip('\n'))
+
+ # if needed, store the last RRs (they are not followed by 'var=' mark)
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+
+ return result
+
+def generate_variables(out_file, rrsets_data):
+ '''Generate a C++ source file containing a C-string variables for RRs.
+
+ This produces a definition of C-string for each RRset that is expected
+ to be named as follows:
+ const char* const var_name =
+ "example.com. 3600 IN A 192.0.2.1\n"
+ "example.com. 3600 IN A 192.0.2.2\n";
+
+ Escape character '\' in the string will be further escaped so it will
+ compile.
+
+ '''
+ with open(out_file, 'w') as out:
+ for (var_name, rrs) in rrsets_data:
+ if len(var_name) > 0:
+ out.write('const char* const ' + var_name + ' =\n')
+ # Combine all RRs, escaping '\' as a C-string
+ out.write('\n'.join([' \"%s\\n\"' %
+ (rr.replace('\\', '\\\\'))
+ for rr in rrs]))
+ out.write(';\n')
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ sys.stderr.write('gen-query-testdata.py require 2 args\n')
+ sys.exit(1)
+ rrsets_data = parse_input(sys.argv[1])
+ generate_variables(sys.argv[2], rrsets_data)
+
diff --git a/src/bin/auth/tests/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc
deleted file mode 100644
index f587ba2..0000000
--- a/src/bin/auth/tests/query_inmemory_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dns/name.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <cc/data.h>
-
-#include <datasrc/client_list.h>
-
-#include <auth/query.h>
-
-#include <testutils/dnsmessage_test.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::testutils;
-using isc::datasrc::ConfigurableClientList;
-using std::string;
-
-namespace {
-
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// This is not inside the zone, this is created at runtime
-const char* const synthetized_cname_txt =
- "www.dname.example.com. 3600 IN CNAME "
- "www.somethinglong.dnametarget.example.com.\n";
-
-// This is a subset of QueryTest using (subset of) the same test data, but
-// with the production in-memory data source. Both tests should be eventually
-// unified to avoid duplicates.
-class InMemoryQueryTest : public ::testing::Test {
-protected:
- InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) {
- response.setRcode(Rcode::NOERROR());
- response.setOpcode(Opcode::QUERY());
- list.configure(isc::data::Element::fromJSON(
- "[{\"type\": \"MasterFiles\","
- " \"cache-enable\": true, "
- " \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_DIR "/example.zone") +
- "\"}}]"), true);
- }
-
- ConfigurableClientList list;
- Message response;
- Query query;
-};
-
-// A wrapper to check resulting response message commonly used in
-// tests below.
-// check_origin needs to be specified only when the authority section has
-// an SOA RR. The interface is not generic enough but should be okay
-// for our test cases in practice.
-void
-responseCheck(Message& response, const isc::dns::Rcode& rcode,
- unsigned int flags, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount,
- const char* const expected_answer,
- const char* const expected_authority,
- const char* const expected_additional,
- const Name& check_origin = Name::ROOT_NAME())
-{
- // In our test cases QID, Opcode, and QDCOUNT should be constant, so
- // we don't bother the test cases specifying these values.
- headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
- flags, 0, ancount, nscount, arcount);
- if (expected_answer != NULL) {
- rrsetsCheck(expected_answer,
- response.beginSection(Message::SECTION_ANSWER),
- response.endSection(Message::SECTION_ANSWER),
- check_origin);
- }
- if (expected_authority != NULL) {
- rrsetsCheck(expected_authority,
- response.beginSection(Message::SECTION_AUTHORITY),
- response.endSection(Message::SECTION_AUTHORITY),
- check_origin);
- }
- if (expected_additional != NULL) {
- rrsetsCheck(expected_additional,
- response.beginSection(Message::SECTION_ADDITIONAL),
- response.endSection(Message::SECTION_ADDITIONAL));
- }
-}
-
-/*
- * Test a query under a domain with DNAME. We should get a synthetized CNAME
- * as well as the DNAME.
- *
- * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
- * as well. This includes tests pointing inside the zone, outside the zone,
- * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
- */
-TEST_F(InMemoryQueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
- response);
-
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
- (string(dname_txt) + synthetized_cname_txt).c_str(),
- NULL, NULL);
-}
-}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 84b7f8a..a22d2d7 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <map>
-#include <sstream>
-#include <vector>
-
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/static_assert.hpp>
@@ -24,9 +20,12 @@
#include <dns/masterload.h>
#include <dns/message.h>
+#include <dns/master_loader.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
+#include <dns/rrcollator.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
#include <dns/rdataclass.h>
@@ -40,6 +39,12 @@
#include <gtest/gtest.h>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -49,7 +54,7 @@ using namespace isc::testutils;
namespace {
-// Simple wrapper for a sincle data source client.
+// Simple wrapper for a single data source client.
// The list simply delegates all the answers to the single
// client.
class SingletonList : public ClientList {
@@ -79,150 +84,21 @@ private:
DataSourceClient& client_;
};
+// These are commonly used data (auto-generated). There are some exceptional
+// data that are only used in a limited scenario, which are defined separately
+// below.
+#include <auth/tests/example_base_inc.cc>
+#include <auth/tests/example_nsec3_inc.cc>
-// This is the content of the mock zone (see below).
-// It's a sequence of textual RRs that is supposed to be parsed by
-// dns::masterLoad(). Some of the RRs are also used as the expected
-// data in specific tests, in which case they are referenced via specific
-// local variables (such as soa_txt).
-//
-// For readability consistency, all strings are placed in a separate line,
-// even if they are very short and can reasonably fit in a single line with
-// the corresponding variable. For example, we write
-// const char* const foo_txt =
-// "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-// instead of
-// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-const char* const soa_txt =
- "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
-const char* const zone_ns_txt =
- "example.com. 3600 IN NS glue.delegation.example.com.\n"
- "example.com. 3600 IN NS noglue.example.com.\n"
- "example.com. 3600 IN NS example.net.\n";
+// This is used only in one pathological test case.
const char* const zone_ds_txt =
"example.com. 3600 IN DS 57855 5 1 "
"B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const ns_addrs_txt =
- "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
- "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
- "noglue.example.com. 3600 IN A 192.0.2.53\n";
-const char* const delegation_txt =
- "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
- "delegation.example.com. 3600 IN NS noglue.example.com.\n"
- "delegation.example.com. 3600 IN NS cname.example.com.\n"
- "delegation.example.com. 3600 IN NS example.org.\n";
-// Borrowed from the RFC4035
-const char* const delegation_ds_txt =
- "delegation.example.com. 3600 IN DS 57855 5 1 "
- "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const mx_txt =
- "mx.example.com. 3600 IN MX 10 www.example.com.\n"
- "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
- "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt =
- "www.example.com. 3600 IN A 192.0.2.80\n";
-const char* const cname_txt =
- "cname.example.com. 3600 IN CNAME www.example.com.\n";
-const char* const cname_nxdom_txt =
- "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
-// CNAME Leading out of zone
-const char* const cname_out_txt =
- "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// Some data at the dname node (allowed by RFC 2672)
-const char* const dname_a_txt =
- "dname.example.com. 3600 IN A 192.0.2.5\n";
+
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
"www.dname.example.com. 3600 IN CNAME "
"www.somethinglong.dnametarget.example.com.\n";
-// The rest of data won't be referenced from the test cases.
-const char* const other_zone_rrs =
- "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
- "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
- "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
-// Wildcards
-const char* const wild_txt =
- "*.wild.example.com. 3600 IN A 192.0.2.7\n";
-const char* const nsec_wild_txt =
- "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
-const char* const cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN NSEC "
- "delegation.example.com. CNAME NSEC RRSIG\n";
-// Wildcard_nxrrset
-const char* const wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
-const char* const nsec_wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
-const char* const wild_txt_next =
- "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
-const char* const nsec_wild_txt_next =
- "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
-// Wildcard empty
-const char* const empty_txt =
- "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
-const char* const nsec_empty_txt =
- "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt =
- "t.example.com. 3600 IN A 192.0.2.15\n";
-const char* const nsec_empty_prev_txt =
- "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
-// Used in NXDOMAIN proof test. We are going to test some unusual case where
-// the best possible wildcard is below the "next domain" of the NSEC RR that
-// proves the NXDOMAIN, i.e.,
-// mx.example.com. (exist)
-// (.no.example.com. (qname, NXDOMAIN)
-// ).no.example.com. (exist)
-// *.no.example.com. (best possible wildcard, not exist)
-const char* const no_txt =
- ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
-// NSEC records.
-const char* const nsec_apex_txt =
- "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
-const char* const nsec_mx_txt =
- "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
-const char* const nsec_no_txt =
- ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
-// We'll also test the case where a single NSEC proves both NXDOMAIN and the
-// non existence of wildcard. The following records will be used for that
-// test.
-// ).no.example.com. (exist, whose NSEC proves everything)
-// *.no.example.com. (best possible wildcard, not exist)
-// nx.no.example.com. (NXDOMAIN)
-// nz.no.example.com. (exist)
-const char* const nz_txt =
- "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
-const char* const nsec_nz_txt =
- "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
-const char* const nsec_nxdomain_txt =
- "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
-
-// NSEC for the normal NXRRSET case
-const char* const nsec_www_txt =
- "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
-
-// Authoritative data without NSEC
-const char* const nonsec_a_txt =
- "nonsec.example.com. 3600 IN A 192.0.2.0\n";
-
-// NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-const string nsec3_apex_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
-const string nsec3_apex_rrsig_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
-const string nsec3_www_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-const string nsec3_www_rrsig_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
// NSEC3 for wild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
@@ -246,42 +122,13 @@ const char* const nsec3_uwild_txt =
"t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-// (Secure) delegation data; Delegation with DS record
-const char* const signed_delegation_txt =
- "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const signed_delegation_ds_txt =
- "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
- "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
-
// (Secure) delegation data; Delegation without DS record (and both NSEC
// and NSEC3 denying its existence)
-const char* const unsigned_delegation_txt =
- "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_nsec_txt =
- "unsigned-delegation.example.com. 3600 IN NSEC "
- "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
// This one will be added on demand
const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
-// Delegation without DS record, and no direct matching NSEC3 record
-const char* const unsigned_delegation_optout_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_optout_nsec_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NSEC "
- "*.uwild.example.com. NS RRSIG NSEC\n";
-
-// (Secure) delegation data; Delegation where the DS lookup will raise an
-// exception.
-const char* const bad_delegation_txt =
- "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
-
-// Delegation from an unsigned parent. There's no DS, and there's no NSEC
-// or NSEC3 that proves it.
-const char* const nosec_delegation_txt =
- "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
-
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -314,58 +161,14 @@ textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
return (rrset);
}
-// This is a mock Zone Finder class for testing.
-// It is a derived class of ZoneFinder for the convenient of tests.
-// Its find() method emulates the common behavior of protocol compliant
-// ZoneFinder classes, but simplifies some minor cases and also supports broken
-// behavior.
-// For simplicity, most names are assumed to be "in zone"; delegations
-// to child zones are identified by the existence of non origin NS records.
-// Another special name is "dname.example.com". Query names under this name
-// will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
-class MockZoneFinder : public ZoneFinder {
+// Setup for faked NSEC3 hash used throughout this test.
+class TestNSEC3Hash : public NSEC3Hash {
+private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap hash_map_;
public:
- MockZoneFinder() :
- origin_(Name("example.com")),
- bad_signed_delegation_name_("bad-delegation.example.com"),
- dname_name_("dname.example.com"),
- has_SOA_(true),
- has_apex_NS_(true),
- rrclass_(RRClass::IN()),
- include_rrsig_anyway_(false),
- use_nsec3_(false),
- nsec_name_(origin_),
- nsec3_fake_(NULL),
- nsec3_name_(NULL)
- {
- stringstream zone_stream;
- zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
- cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
- dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
- nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
- nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
- wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
- wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
- nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
- empty_prev_txt << nsec_empty_prev_txt <<
- nsec3_apex_txt << nsec3_www_txt <<
- signed_delegation_txt << signed_delegation_ds_txt <<
- unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
- unsigned_delegation_optout_txt <<
- unsigned_delegation_optout_nsec_txt <<
- bad_delegation_txt << nosec_delegation_txt;
-
- masterLoad(zone_stream, origin_, rrclass_,
- boost::bind(&MockZoneFinder::loadRRset, this, _1));
-
- empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
- RRClass::IN(),
- RRType::NSEC(),
- RRTTL(3600)));
-
+ TestNSEC3Hash() {
// (Faked) NSEC3 hash map. For convenience we use hardcoded built-in
// map instead of calculating and using actual hash.
// The used hash values are borrowed from RFC5155 examples (they are
@@ -411,6 +214,79 @@ public:
hash_map_[Name("www1.uwild.example.com")] =
"q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = hash_map_.find(name);
+ if (found != hash_map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const rdata::generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+public:
+ TestNSEC3HashCreator() {}
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(uint8_t, uint16_t, const uint8_t*, size_t) const {
+ return (new TestNSEC3Hash);
+ }
+};
+
+// This is a mock Zone Finder class for testing.
+// It is a derived class of ZoneFinder for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// ZoneFinder classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZoneFinder : public ZoneFinder {
+public:
+ MockZoneFinder() :
+ origin_(Name("example.com")),
+ bad_signed_delegation_name_("bad-delegation.example.com"),
+ dname_name_("dname.example.com"),
+ has_SOA_(true),
+ has_apex_NS_(true),
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false),
+ use_nsec3_(false),
+ nsec_name_(origin_),
+ nsec3_fake_(NULL),
+ nsec3_name_(NULL)
+ {
+ RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
+ MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
+ origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator.getCallback());
+ loader.load();
+
+ empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
+ RRClass::IN(),
+ RRType::NSEC(),
+ RRTTL(3600)));
+ }
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
@@ -513,6 +389,19 @@ private:
};
void loadRRset(RRsetPtr rrset) {
+ // For simplicity we dynamically generate RRSIGs and add them below.
+ // The RRSIG RDATA should be consistent with that defined in the
+ // zone file.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return;
+ }
+
+ // NSEC3PARAM is not used in the mock data source (and it would confuse
+ // non-NSEC3 test cases).
+ if (rrset->getType() == RRType::NSEC3PARAM()) {
+ return;
+ }
+
if (rrset->getType() == RRType::NSEC3()) {
// NSEC3 should go to the dedicated table
nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
@@ -565,9 +454,7 @@ private:
// Enabled when not NULL
const FindNSEC3Result* nsec3_fake_;
const Name* nsec3_name_;
-public:
- // Public, to allow tests looking up the right names for something
- map<Name, string> hash_map_;
+ TestNSEC3Hash nsec3_hash_;
};
// A helper function that generates a new RRset based on "wild_rrset",
@@ -627,11 +514,7 @@ MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
// For brevity, we assume several things below: maps should have an
// expected entry when operator[] is used; maps are not empty.
for (int i = 0; i < labels; ++i) {
- const string hlabel = hash_map_[name.split(i, labels - i)];
- if (hlabel.empty()) {
- isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
- name.split(i, labels - i));
- }
+ const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
const Name hname = Name(hlabel + ".example.com");
// We don't use const_iterator so that we can use operator[] below
Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
@@ -893,10 +776,55 @@ MockZoneFinder::find(const Name& name, const RRType& type,
return (createContext(options,NXDOMAIN, RRsetPtr()));
}
-class QueryTest : public ::testing::Test {
+enum DataSrcType {
+ MOCK,
+ INMEMORY,
+ SQLITE3
+};
+
+boost::shared_ptr<ClientList>
+createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
+ boost::shared_ptr<ConfigurableClientList> list;
+ switch (type) {
+ case MOCK:
+ return (boost::shared_ptr<ClientList>(new SingletonList(client)));
+ case INMEMORY:
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ "\"}}]"), true);
+ return (list);
+ case SQLITE3:
+ // The copy should succeed; if it failed we should notice it in
+ // test cases. However, we check the return value to avoid problems
+ // in some glibcs where "system()" is annotated with the "warn unused
+ // result" attribute.
+ EXPECT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied"));
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied") +
+ "\"}}]"), true);
+ return (list);
+ default:
+ isc_throw(isc::Unexpected,
+ "Unexpected data source type, should be a bug in test code");
+ }
+}
+
+class QueryTest : public ::testing::TestWithParam<DataSrcType> {
protected:
QueryTest() :
- list(memory_client),
qname(Name("www.example.com")), qclass(RRClass::IN()),
qtype(RRType::A()), response(Message::RENDER),
qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -906,22 +834,166 @@ protected:
"glue.delegation.example.com. 3600 IN RRSIG " +
getCommonRRSIGText("AAAA") + "\n" +
"noglue.example.com. 3600 IN RRSIG " +
- getCommonRRSIGText("A"))
+ getCommonRRSIGText("A")),
+ base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
+ nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
+ common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
+ rrsets_added_(false)
{
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3hash_creator_);
+
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
// create and add a matching zone.
mock_finder = new MockZoneFinder();
memory_client.addZone(ZoneFinderPtr(mock_finder));
}
+
+ virtual void SetUp() {
+ // clear the commonly included zone file.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+
+ // We create data source clients here, not in the constructor, so this
+ // doesn't happen for derived test class. This also ensures the
+ // data source clients are configured after setting NSEC3 hash in case
+ // there's dependency.
+ list_ = createDataSrcClientList(GetParam(), memory_client);
+ }
+
+ virtual void TearDown() {
+ // make sure to clear the commonly included zone file to prevent
+ // any remaining contents from affecting the next test.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+ }
+
+ virtual ~QueryTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ void enableNSEC3(const vector<string>& rrsets_to_add) {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ switch (GetParam()) {
+ case MOCK:
+ mock_finder->setNSEC3Flag(true);
+ addRRsets(rrsets_to_add, *list_, "");
+ break;
+ case INMEMORY:
+ addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
+ break;
+ case SQLITE3:
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied"));
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied") +
+ "\"}}]"), true);
+ addRRsets(rrsets_to_add, *new_list, "");
+ list_ = new_list;
+ break;
+ }
+ }
+
+ // A helper to add some RRsets to the test zone in the middle of a test
+ // case. The detailed behavior is quite different depending on the
+ // data source type, and not all parameters are used in all cases.
+ //
+ // Note: due to limitation of its implementation, this method doesn't
+ // work correctly for in-memory if called more than once. This condition
+ // is explicitly checked so any accidental violation would be noted as a
+ // test failure.
+ void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
+ const string& zone_file)
+ {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ ofstream ofs;
+
+ switch (GetParam()) {
+ case MOCK:
+ // directly add them to the mock data source; ignore the passed
+ // list.
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ mock_finder->addRecord(*it);
+ }
+ break;
+ case INMEMORY:
+ ASSERT_FALSE(rrsets_added_);
+ rrsets_added_ = true;
+
+ // dump the RRsets to be added to the placeholder of commonly
+ // included zone file (removing any existing contents) and do
+ // full reconfiguration.
+ ofs.open(common_zone_file.c_str(), ios_base::trunc);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ofs << *it << "\n";
+ ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
+ }
+ ofs.close();
+
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ zone_file + "\"}}]"), true);
+ list_ = new_list;
+ break;
+ case SQLITE3:
+ const Name origin("example.com");
+ ZoneUpdaterPtr updater =
+ list.find(origin, true, false).dsrc_client_->
+ getUpdater(origin, false);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ConstRRsetPtr rrset = textToRRset(*it);
+ updater->addRRset(*rrset);
+ updater->addRRset(*createRRSIG(rrset));
+ }
+ updater->commit();
+ break;
+ }
+ }
+
+private:
+ // A helper for enableNSEC3, creating an RRSIG RRset for the corresponding
+ // non-sig RRset, using the commonly used parameters.
+ static ConstRRsetPtr createRRSIG(ConstRRsetPtr rrset) {
+ RRsetPtr sig_rrset(new RRset(rrset->getName(), rrset->getClass(),
+ RRType::RRSIG(), rrset->getTTL()));
+ sig_rrset->addRdata(generic::RRSIG(
+ getCommonRRSIGText(rrset->getType().
+ toText())));
+ return (sig_rrset);
+ }
+
+protected:
MockZoneFinder* mock_finder;
// We use InMemoryClient here. We could have some kind of mock client
// here, but historically, the Query supported only InMemoryClient
// (originally named MemoryDataSrc) and was tested with it, so we keep
// it like this for now.
InMemoryClient memory_client;
- // A wrapper client list to wrap the single data source.
- SingletonList list;
+
+ boost::shared_ptr<ClientList> list_;
const Name qname;
const RRClass qclass;
const RRType qtype;
@@ -930,6 +1002,37 @@ protected:
const uint16_t query_code;
const string ns_addrs_and_sig_txt; // convenient shortcut
Query query;
+ TestNSEC3Hash nsec3_hash_;
+ vector<string> rrsets_to_add_;
+ const string base_zone_file;
+private:
+ const string nsec3_zone_file;
+ const string common_zone_file;
+ const TestNSEC3HashCreator nsec3hash_creator_;
+ bool rrsets_added_;
+};
+
+// We test the in-memory and SQLite3 data source implementations. SQLite3
+// will require a loadable module, which doesn't work with static link for
+// all platforms.
+INSTANTIATE_TEST_CASE_P(, QueryTest,
+ ::testing::Values(MOCK, INMEMORY
+#ifndef USE_STATIC_LINK
+ , SQLITE3
+#endif
+ ));
+
+// This inherit the QueryTest cases except for the parameterized setup;
+// it's intended to be used selected test cases that only work for mock
+// data sources either because of some limitation or because of the type of
+// tests (relying on a "broken" data source behavior that can't happen with
+// production data source implementations).
+class QueryTestForMockOnly : public QueryTest {
+protected:
+ // Override SetUp() to avoid parameterized setup
+ virtual void SetUp() {
+ list_ = createDataSrcClientList(MOCK, memory_client);
+ }
};
// A wrapper to check resulting response message commonly used in
@@ -969,7 +1072,7 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
}
}
-TEST_F(QueryTest, noZone) {
+TEST_P(QueryTest, noZone) {
// There's no zone in the memory datasource. So the response should have
// REFUSED.
InMemoryClient empty_memory_client;
@@ -979,15 +1082,15 @@ TEST_F(QueryTest, noZone) {
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
-TEST_F(QueryTest, exactMatch) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatch) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchMultipleQueries) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatchMultipleQueries) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -996,27 +1099,26 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
response.clear(isc::dns::Message::RENDER);
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
SCOPED_TRACE("Second query");
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchIgnoreSIG) {
+TEST_P(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
mock_finder->setIncludeRRSIGAnyway(true);
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, dnssecPositive) {
+TEST_P(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
- EXPECT_NO_THROW(query.process(list, qname, qtype, response,
- true));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response, true));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
(www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
@@ -1031,10 +1133,10 @@ TEST_F(QueryTest, dnssecPositive) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, exactAddrMatch) {
+TEST_P(QueryTest, exactAddrMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("noglue.example.com"),
qtype, response));
@@ -1044,10 +1146,10 @@ TEST_F(QueryTest, exactAddrMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexNSMatch) {
+TEST_P(QueryTest, apexNSMatch) {
// find match rrset, omit authority data which has already been provided
// in the answer section from the authority section.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::NS(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1055,10 +1157,16 @@ TEST_F(QueryTest, apexNSMatch) {
}
// test type any query logic
-TEST_F(QueryTest, exactAnyMatch) {
+TEST_P(QueryTest, exactAnyMatch) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1069,10 +1177,10 @@ TEST_F(QueryTest, exactAnyMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexAnyMatch) {
+TEST_P(QueryTest, apexAnyMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
(string(soa_txt) + string(zone_ns_txt) +
@@ -1080,23 +1188,23 @@ TEST_F(QueryTest, apexAnyMatch) {
NULL, ns_addrs_txt, mock_finder->getOrigin());
}
-TEST_F(QueryTest, mxANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
+TEST_P(QueryTest, mxANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("mx.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
(string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
(string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
-TEST_F(QueryTest, glueANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
+TEST_P(QueryTest, glueANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("delegation.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, nodomainANY) {
- EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
+TEST_P(QueryTest, nodomainANY) {
+ EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1105,17 +1213,20 @@ TEST_F(QueryTest, nodomainANY) {
// This tests that when we need to look up Zone's apex NS records for
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
-TEST_F(QueryTest, noApexNS) {
+//
+// This only works with mock data source (for production datasrc the
+// post-load would reject such a zone)
+TEST_F(QueryTestForMockOnly, noApexNS) {
// Disable apex NS record
mock_finder->setApexNSFlag(false);
- EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("noglue.example.com"), qtype,
response), Query::NoApexNS);
// We don't look into the response, as it threw
}
-TEST_F(QueryTest, delegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, delegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
qtype, response));
@@ -1123,19 +1234,19 @@ TEST_F(QueryTest, delegation) {
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, delegationWithDNSSEC) {
+TEST_P(QueryTest, delegationWithDNSSEC) {
// Similar to the previous one, but with requesting DNSSEC.
// In this case the parent zone would behave as unsigned, so the result
// should be just like non DNSSEC delegation.
- query.process(list, Name("www.nosec-delegation.example.com"),
+ query.process(*list_, Name("www.nosec-delegation.example.com"),
qtype, response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
NULL, nosec_delegation_txt, NULL);
}
-TEST_F(QueryTest, secureDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.signed-delegation.example.com"),
qtype, response, true));
@@ -1149,8 +1260,8 @@ TEST_F(QueryTest, secureDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureUnsignedDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true));
@@ -1164,33 +1275,33 @@ TEST_F(QueryTest, secureUnsignedDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3) {
// Similar to the previous case, but the zone is signed with NSEC3,
// and this delegation is NOT an optout.
- const Name insecurechild_name("unsigned-delegation.example.com");
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
// The response should contain the NS and matching NSEC3 with its RRSIG
+ const Name insecurechild_name("unsigned-delegation.example.com");
responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
NULL,
(string(unsigned_delegation_txt) +
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[insecurechild_name] +
+ nsec3_hash_.calculate(insecurechild_name) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
// Similar to the previous case, but the delegation is an optout.
- mock_finder->setNSEC3Flag(true);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
@@ -1202,44 +1313,46 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
NULL,
(string(unsigned_delegation_txt) +
string(nsec3_apex_txt) +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com")] +
+ nsec3_hash_.calculate(Name("www.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, badSecureDelegation) {
+TEST_F(QueryTestForMockOnly, badSecureDelegation) {
+ // This is a broken data source scenario; works only with mock.
+
// Test whether exception is raised if DS query at delegation results in
// something different than SUCCESS or NXRRSET
- EXPECT_THROW(query.process(list,
+ EXPECT_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response, true), Query::BadDS);
// But only if DNSSEC is requested (it shouldn't even try to look for
// the DS otherwise)
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response));
}
-TEST_F(QueryTest, nxdomain) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, nxdomain) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC) {
+TEST_P(QueryTest, nxdomainWithNSEC) {
// NXDOMAIN with DNSSEC proof. We should have SOA, NSEC that proves
// NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
// as well as their RRSIGs.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response, true));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1255,12 +1368,18 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC2) {
+TEST_P(QueryTest, nxdomainWithNSEC2) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about no_txt. In this case the best possible wildcard
// is derived from the next domain of the NSEC that proves NXDOMAIN, and
// the NSEC to provide the non existence of wildcard is different from
// the first NSEC.
- query.process(list, Name("(.no.example.com"), qtype, response,
+ query.process(*list_, Name("(.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1275,10 +1394,17 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
+TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work. This is probably the same type of bug
+ // as nxdomainWithNSEC2 (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about nz_txt. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence of wildcard.
- query.process(list, Name("nx.no.example.com"), qtype, response,
+ query.process(*list_, Name("nx.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
NULL, (string(soa_txt) +
@@ -1290,52 +1416,62 @@ TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC1) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC1) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
mock_finder->setNSECResult(Name("badnsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("badnsec.example.com"),
qtype, response, true),
std::bad_cast);
}
-TEST_F(QueryTest, nxdomainBadNSEC2) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC2) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with an empty NSEC RR.
mock_finder->setNSECResult(Name("emptynsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("emptynsec.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC3) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns SUCCESS. it should be NXDOMAIN.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC4) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC4) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC5) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns non NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
// This is a bit odd, but we'll simply include the returned RRset.
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1350,28 +1486,30 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC6) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC6) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxrrset) {
- EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
+TEST_P(QueryTest, nxrrset) {
+ EXPECT_NO_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC) {
+TEST_P(QueryTest, nxrrsetWithNSEC) {
// NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1521,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, emptyNameWithNSEC) {
+TEST_P(QueryTest, emptyNameWithNSEC) {
// Empty non terminal with DNSSEC proof. This is one of the cases of
// Section 3.1.3.2 of RFC4035.
// mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
@@ -1392,7 +1530,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
// exact match), so we only need one NSEC.
// From the point of the Query::process(), this is actually no different
// from the other NXRRSET case, but we check that explicitly just in case.
- query.process(list, Name("no.example.com"), RRType::A(),
+ query.process(*list_, Name("no.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1404,11 +1542,11 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithoutNSEC) {
+TEST_P(QueryTest, nxrrsetWithoutNSEC) {
// NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
// This is an unexpected event (if the zone is supposed to be properly
// signed with NSECs), but we accept and ignore the oddity.
- query.process(list, Name("nonsec.example.com"), RRType::TXT(),
+ query.process(*list_, Name("nonsec.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1417,10 +1555,10 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC) {
+TEST_P(QueryTest, wildcardNSEC) {
// The qname matches *.wild.example.com. The response should contain
// an NSEC that proves the non existence of a closer name.
- query.process(list, Name("www.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "www") +
@@ -1437,10 +1575,10 @@ TEST_F(QueryTest, wildcardNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC) {
+TEST_P(QueryTest, CNAMEwildNSEC) {
// Similar to the previous case, but the matching wildcard record is
// CNAME.
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
@@ -1453,17 +1591,17 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC3) {
+TEST_P(QueryTest, wildcardNSEC3) {
// Similar to wildcardNSEC, but the zone is signed with NSEC3.
// The next closer is y.wild.example.com, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the apex.
- mock_finder->setNSEC3Flag(true);
-
- // This is NSEC3 for wild.example.com, which will be used in the middle
+ //
+ // Adding NSEC3 for wild.example.com, which will be used in the middle
// of identifying the next closer name.
- mock_finder->addRecord(nsec3_atwild_txt);
+ rrsets_to_add_.push_back(nsec3_atwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("x.y.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("x.y.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "x.y") +
@@ -1474,35 +1612,37 @@ TEST_F(QueryTest, wildcardNSEC3) {
getCommonRRSIGText("NS") + "\n" +
// NSEC3 for the wildcard proof and its RRSIG
string(nsec3_apex_txt) +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC3) {
+TEST_P(QueryTest, CNAMEwildNSEC3) {
// Similar to CNAMEwildNSEC, but with NSEC3.
// The next closer is qname itself, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the www.example.com.
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(nsec3_atcnamewild_txt);
+ rrsets_to_add_.push_back(nsec3_atcnamewild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
string("www.cnamewild.example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("CNAME") + "\n").c_str(),
(string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, badWildcardNSEC3) {
+TEST_F(QueryTestForMockOnly, badWildcardNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNSEC3, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1511,46 +1651,58 @@ TEST_F(QueryTest, badWildcardNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, badWildcardProof1) {
+TEST_F(QueryTestForMockOnly, badWildcardProof1) {
+ // This is a broken data source scenario; works only with mock.
+
// Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
// when NXDOMAIN is expected.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof2) {
+TEST_F(QueryTestForMockOnly, badWildcardProof2) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof3) {
+TEST_F(QueryTestForMockOnly, badWildcardProof3) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// NXRRSET on WILDCARD with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence RRSETs of wildcard.
- query.process(list, Name("www.wild.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1562,12 +1714,18 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// WILDCARD + NXRRSET with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence RRSETs of
// wildcard.
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1582,15 +1740,15 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
// Similar to the previous case, but providing NSEC3 proofs according to
// RFC5155 Section 7.2.5.
- mock_finder->addRecord(nsec3_wild_txt);
- mock_finder->addRecord(nsec3_uwild_txt);
- mock_finder->setNSEC3Flag(true);
+ rrsets_to_add_.push_back(nsec3_wild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,23 +1757,25 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_uwild_txt) +
- mock_finder->hash_map_[Name("uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(nsec3_wild_txt) +
- mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("*.uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Collision) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1624,15 +1784,17 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Broken) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
// wildcard name will be returned. This shouldn't happen in a reasonably
- // NSEC-signed zone, and should result in an exception.
+ // NSEC3-signed zone, and should result in an exception.
mock_finder->setNSEC3Flag(true);
const Name wname("*.uwild.example.com.");
ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
@@ -1641,16 +1803,16 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
mock_finder->addRecord(nsec3_wild_txt);
mock_finder->addRecord(nsec3_uwild_txt);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardEmptyWithNSEC) {
+TEST_P(QueryTest, wildcardEmptyWithNSEC) {
// Empty WILDCARD with DNSSEC proof. We should have SOA, NSEC that proves
// the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence wildcard.
- query.process(list, Name("a.t.example.com"), RRType::A(),
+ query.process(*list_, Name("a.t.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1669,24 +1831,26 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
* This tests that when there's no SOA and we need a negative answer. It should
* throw in that case.
*/
-TEST_F(QueryTest, noSOA) {
+TEST_F(QueryTestForMockOnly, noSOA) {
+ // This is a broken data source scenario; works only with mock.
+
// disable zone's SOA RR.
mock_finder->setSOAFlag(false);
// The NX Domain
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
- EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxrrset.example.com"),
qtype, response), Query::NoSOA);
}
-TEST_F(QueryTest, noMatchZone) {
+TEST_P(QueryTest, noMatchZone) {
// there's a zone in the memory datasource but it doesn't match the qname.
// should result in REFUSED.
- query.process(list, Name("example.org"), qtype, response);
+ query.process(*list_, Name("example.org"), qtype, response);
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
@@ -1696,8 +1860,8 @@ TEST_F(QueryTest, noMatchZone) {
* The MX RRset has two RRs, one pointing to a known domain with
* A record, other to unknown out of zone one.
*/
-TEST_F(QueryTest, MX) {
- query.process(list, Name("mx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MX) {
+ query.process(*list_, Name("mx.example.com"), RRType::MX(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1710,8 +1874,8 @@ TEST_F(QueryTest, MX) {
*
* This should not trigger the additional processing for the exchange.
*/
-TEST_F(QueryTest, MXAlias) {
- query.process(list, Name("cnamemx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MXAlias) {
+ query.process(*list_, Name("cnamemx.example.com"), RRType::MX(),
response);
// there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1730,69 +1894,69 @@ TEST_F(QueryTest, MXAlias) {
* TODO: We currently don't do chaining, so only the CNAME itself should be
* returned.
*/
-TEST_F(QueryTest, CNAME) {
- query.process(list, Name("cname.example.com"), RRType::A(),
+TEST_P(QueryTest, CNAME) {
+ query.process(*list_, Name("cname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME) {
+TEST_P(QueryTest, explicitCNAME) {
// same owner name as the CNAME test but explicitly query for CNAME RR.
// expect the same response as we don't provide a full chain yet.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_RRSET) {
+TEST_P(QueryTest, CNAME_NX_RRSET) {
// Leads to www.example.com, it doesn't have TXT
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
- query.process(list, Name("cname.example.com"), RRType::TXT(),
+ query.process(*list_, Name("cname.example.com"), RRType::TXT(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+TEST_P(QueryTest, explicitCNAME_NX_RRSET) {
// same owner name as the NXRRSET test but explicitly query for CNAME RR.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+TEST_P(QueryTest, CNAME_NX_DOMAIN) {
// Leads to nxdomain.example.com
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional,
// RCODE being NXDOMAIN.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
// RCODE being NOERROR.
- query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
+ query.process(*list_, Name("cnamenxdom.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_nxdom_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+TEST_P(QueryTest, explicitCNAME_NX_DOMAIN) {
// same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
- query.process(list, Name("cnamenxdom.example.com"),
+ query.process(*list_, Name("cnamenxdom.example.com"),
RRType::CNAME(), response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_OUT) {
+TEST_P(QueryTest, CNAME_OUT) {
/*
* This leads out of zone. This should have only the CNAME even
* when we do chaining.
@@ -1801,16 +1965,16 @@ TEST_F(QueryTest, CNAME_OUT) {
* Then the same test should be done with .org included there and
* see what it does (depends on what we want to do)
*/
- query.process(list, Name("cnameout.example.com"), RRType::A(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_out_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_OUT) {
+TEST_P(QueryTest, explicitCNAME_OUT) {
// same owner name as the OUT test but explicitly query for CNAME RR.
- query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1825,8 +1989,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
* as well. This includes tests pointing inside the zone, outside the zone,
* pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
*/
-TEST_F(QueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1841,8 +2005,8 @@ TEST_F(QueryTest, DNAME) {
* ANY is handled specially sometimes. We check it is not the case with
* DNAME.
*/
-TEST_F(QueryTest, DNAME_ANY) {
- query.process(list, Name("www.dname.example.com"), RRType::ANY(),
+TEST_P(QueryTest, DNAME_ANY) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::ANY(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1850,8 +2014,8 @@ TEST_F(QueryTest, DNAME_ANY) {
}
// Test when we ask for DNAME explicitly, it does no synthetizing.
-TEST_F(QueryTest, explicitDNAME) {
- query.process(list, Name("dname.example.com"), RRType::DNAME(),
+TEST_P(QueryTest, explicitDNAME) {
+ query.process(*list_, Name("dname.example.com"), RRType::DNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1862,8 +2026,8 @@ TEST_F(QueryTest, explicitDNAME) {
* Request a RRset at the domain with DNAME. It should not synthetize
* the CNAME, it should return the RRset.
*/
-TEST_F(QueryTest, DNAME_A) {
- query.process(list, Name("dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME_A) {
+ query.process(*list_, Name("dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1874,8 +2038,8 @@ TEST_F(QueryTest, DNAME_A) {
* Request a RRset at the domain with DNAME that is not there (NXRRSET).
* It should not synthetize the CNAME.
*/
-TEST_F(QueryTest, DNAME_NX_RRSET) {
- EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
+TEST_P(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(query.process(*list_, Name("dname.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1887,7 +2051,7 @@ TEST_F(QueryTest, DNAME_NX_RRSET) {
* however, should not throw (and crash the server), but respond with
* YXDOMAIN.
*/
-TEST_F(QueryTest, LongDNAME) {
+TEST_P(QueryTest, LongDNAME) {
// A name that is as long as it can be
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
@@ -1895,7 +2059,7 @@ TEST_F(QueryTest, LongDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1907,14 +2071,14 @@ TEST_F(QueryTest, LongDNAME) {
* This tests that we don't reject valid one by some kind of off by
* one mistake.
*/
-TEST_F(QueryTest, MaxLenDNAME) {
+TEST_P(QueryTest, MaxLenDNAME) {
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
// Check the answer is OK
@@ -1963,7 +2127,10 @@ nsec3Check(bool expected_matched, uint8_t expected_labels,
actual_rrsets.end());
}
-TEST_F(QueryTest, findNSEC3) {
+TEST_F(QueryTestForMockOnly, findNSEC3) {
+ // This test is intended to test the mock data source behavior; no need
+ // to do it for others.
+
// In all test cases in the recursive mode, the closest encloser is the
// apex, and result's closest_labels should be the number of apex labels.
// (In non recursive mode closest_labels should be the # labels of the
@@ -1975,7 +2142,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, non recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), false));
}
@@ -1983,7 +2150,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), true));
}
@@ -1992,7 +2159,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, non recursive");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"),
false));
}
@@ -2002,7 +2169,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
}
@@ -2012,7 +2179,8 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, next closer != qname");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nx.domain.example.com"),
true));
@@ -2022,14 +2190,14 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("largest");
nsec3Check(false, 4,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain2.example.com"),
false));
}
{
SCOPED_TRACE("smallest");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain3.example.com"),
false));
}
@@ -2100,14 +2268,17 @@ private:
const bool have_ds_;
};
-TEST_F(QueryTest, dsAboveDelegation) {
+TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
Name("delegation.example.com"))));
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
RRType::DS(), response, true));
@@ -2121,7 +2292,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, dsAboveDelegationNoData) {
+TEST_P(QueryTest, dsAboveDelegationNoData) {
// Similar to the previous case, but the query is for an unsigned zone
// (which doesn't have a DS at the parent). The response should be a
// "no data" error. The query should still be handled at the parent.
@@ -2131,7 +2302,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("unsigned-delegation.example.com"),
RRType::DS(), response, true));
@@ -2148,8 +2319,8 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// This one checks that type-DS query results in a "no data" response
// when it happens to be sent to the child zone, as described in RFC 4035,
// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
-TEST_F(QueryTest, dsBelowDelegation) {
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2164,9 +2335,10 @@ TEST_F(QueryTest, dsBelowDelegation) {
// Similar to the previous case, but even more pathological: the DS somehow
// exists in the child zone. The Query module should still return SOA.
// In our implementation NSEC/NSEC3 isn't attached in this case.
-TEST_F(QueryTest, dsBelowDelegationWithDS) {
- mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegationWithDS) {
+ rrsets_to_add_.push_back(zone_ds_txt);
+ addRRsets(rrsets_to_add_, *list_, base_zone_file);
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2178,16 +2350,16 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
// DS query received at a completely irrelevant (neither parent nor child)
// server. It should just like the "noZone" test case, but DS query involves
// special processing, so we test it explicitly.
-TEST_F(QueryTest, dsNoZone) {
- query.process(list, Name("example"), RRType::DS(), response,
+TEST_P(QueryTest, dsNoZone) {
+ query.process(*list_, Name("example"), RRType::DS(), response,
true);
responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
}
// DS query for a "grandchild" zone. This should result in normal
// delegation (unless this server also has authority of the grandchild zone).
-TEST_F(QueryTest, dsAtGrandParent) {
- query.process(list, Name("grand.delegation.example.com"),
+TEST_P(QueryTest, dsAtGrandParent) {
+ query.process(*list_, Name("grand.delegation.example.com"),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
(string(delegation_txt) + string(delegation_ds_txt) +
@@ -2201,12 +2373,15 @@ TEST_F(QueryTest, dsAtGrandParent) {
// side and should result in no data with SOA. Note that the server doesn't
// have authority for the "parent". Unlike the dsAboveDelegation test case
// the query should be handled in the child zone, not in the grandparent.
-TEST_F(QueryTest, dsAtGrandParentAndChild) {
+TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
const Name childname("grand.delegation.example.com");
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(childname)));
- query.process(list, childname, RRType::DS(), response, true);
+ query.process(*list_, childname, RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
childname.toText() + " 3600 IN RRSIG " +
@@ -2220,11 +2395,14 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
// DS query for the root name (quite pathological). Since there's no "parent",
// the query will be handled in the root zone anyway, and should (normally)
// result in no data.
-TEST_F(QueryTest, dsAtRoot) {
+TEST_F(QueryTestForMockOnly, dsAtRoot) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
// Pretend to be a root server.
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME())));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2237,11 +2415,14 @@ TEST_F(QueryTest, dsAtRoot) {
// Even more pathological case: A faked root zone actually has its own DS
// query. How we respond wouldn't matter much in practice, but check if
// it behaves as it's intended. This implementation should return the DS.
-TEST_F(QueryTest, dsAtRootWithDS) {
+TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME(),
true)));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2253,19 +2434,19 @@ TEST_F(QueryTest, dsAtRootWithDS) {
}
// Check the signature is present when an NXRRSET is returned
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
// NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_www_txt) + "\n" +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
@@ -2273,7 +2454,9 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
// Check the exception is correctly raised when the NSEC3 thing isn't in the
// zone
-TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
mock_finder->setNSEC3Flag(true);
// We just need it to return false for "matched". This indicates
// there's no exact match for NSEC3 on www.example.com.
@@ -2281,67 +2464,67 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, but does have a matching NSEC3 record
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation.example.com."),
+ query.process(*list_, Name("unsigned-delegation.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, and no directly matching NSEC3 record
// So the response should contain closest encloser proof (and the
// 'next closer' should have opt-out set, though that is not
// actually checked)
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation-optout.example.com."),
+ query.process(*list_, Name("unsigned-delegation-optout.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
// Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
- // Enable NSEC3
- mock_finder->setNSEC3Flag(true);
// This will be the covering NSEC3 for the next closer
- mock_finder->addRecord(nsec3_uwild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
// This will be the covering NSEC3 for the possible wildcard
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ // Enable NSEC3
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
// SOA + its RRSIG
@@ -2350,24 +2533,26 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_uwild_txt) + "\n" +
- mock_finder->hash_map_[Name("uwild.example.com")] +
+ nsec3_hash_.calculate(Name("uwild.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[
- Name("unsigned-delegation.example.com")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadNextNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -2376,12 +2561,14 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadWildcardNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
// NSEC3 for the possible wildcard name, emulating run-time collision.
// This should result in BadNSEC3 exception.
@@ -2395,7 +2582,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3, &wname);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true),
Query::BadNSEC3);
}
@@ -2403,10 +2590,13 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
// The following are tentative tests until we really add tests for the
// query logic for these cases. At that point it's probably better to
// clean them up.
-TEST_F(QueryTest, emptyNameWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
- ZoneFinderContextPtr result = mock_finder->find(
- Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+TEST_P(QueryTest, emptyNameWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
+ const Name qname("no.example.com");
+ ASSERT_TRUE(list_->find(qname).finder_);
+ ZoneFinderContextPtr result =
+ list_->find(qname).finder_->find(qname, RRType::A(),
+ ZoneFinder::FIND_DNSSEC);
EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
EXPECT_FALSE(result->rrset);
EXPECT_TRUE(result->isNSEC3Signed());
@@ -2447,7 +2637,9 @@ loadRRsetVector() {
loadRRsetVectorCallback);
}
-TEST_F(QueryTest, DuplicateNameRemoval) {
+// Note: this is an independent test; don't have to be in the QueryTest
+// fixture.
+TEST(QueryTestSingle, DuplicateNameRemoval) {
// Load some RRsets into the master vector.
loadRRsetVector();
diff --git a/src/bin/auth/tests/testdata/.gitignore b/src/bin/auth/tests/testdata/.gitignore
index b2e0e50..37acc8a 100644
--- a/src/bin/auth/tests/testdata/.gitignore
+++ b/src/bin/auth/tests/testdata/.gitignore
@@ -6,3 +6,13 @@
/shortanswer_fromWire.wire
/simplequery_fromWire.wire
/simpleresponse_fromWire.wire
+/example-base.sqlite3
+/example-base.sqlite3.copied
+/example-base.zone
+/example-base.zone
+/example-common-inc.zone
+/example-nsec3-inc.zone
+/example-nsec3.sqlite3
+/example-nsec3.sqlite3.copied
+/example-nsec3.zone
+/example.zone
diff --git a/src/bin/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am
index a4ea1a5..fed498a 100644
--- a/src/bin/auth/tests/testdata/Makefile.am
+++ b/src/bin/auth/tests/testdata/Makefile.am
@@ -1,4 +1,6 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
+CLEANFILES += example-base.sqlite3 example-nsec3.sqlite3
+CLEANFILES += example-common-inc.zone
BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -24,5 +26,8 @@ EXTRA_DIST += example.com
EXTRA_DIST += example.zone
EXTRA_DIST += example.sqlite3
+EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone
+EXTRA_DIST += example-common-inc-template.zone
+
.spec.wire:
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/bin/auth/tests/testdata/example-base-inc.zone b/src/bin/auth/tests/testdata/example-base-inc.zone
new file mode 100644
index 0000000..bbcbef1
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base-inc.zone
@@ -0,0 +1,236 @@
+;; This file defines a set of RRs commonly used in query tests in the
+;; form of standard master zone file.
+;;
+;; It's a sequence of the following pattern:
+;; ;var=<var_name>
+;; RR_1
+;; RR_2
+;; ..
+;; RR_n
+;;
+;; where var_name is a string that can be used as a variable name in a
+;; C/C++ source file or an empty string. RR_x is a single-line
+;; textual representation of an arbitrary DNS RR.
+;;
+;; If var_name is non empty, the generator script will define a C
+;; variable of C-string type for that set of RRs so that it can be referred
+;; to in the test source file.
+;;
+;; Note that lines beginning ';var=' is no different from other
+;; comment lines as a zone file. It has special meaning only for the
+;; generator script. Obviously, real comment lines cannot begin with
+;; ';var=' (which should be less likely to happen in practice though).
+;;
+;; These RRs will be loaded into in-memory data source in that order.
+;; Note that it may impose stricter restriction on the order of RRs.
+;; In general, each RRset of the same name and type and its RRSIG (if
+;; any) is expected to be grouped.
+
+;var=soa_txt
+example.com. 3600 IN SOA . . 1 0 0 0 0
+;var=zone_ns_txt
+example.com. 3600 IN NS glue.delegation.example.com.
+example.com. 3600 IN NS noglue.example.com.
+example.com. 3600 IN NS example.net.
+
+;var=
+example.com. 3600 IN RRSIG SOA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+example.com. 3600 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Note: the position of the next RR is tricky. It's placed here to
+;; be grouped with the subsequent A RR of the name. But we also want
+;; to group the A RR with other RRs of a different owner name, so the RRSIG
+;; cannot be placed after the A RR. The empty 'var=' specification is
+;; not necessary here, but in case we want to reorganize the ordering
+;; (in which case it's more likely to be needed), we keep it here.
+;var=
+noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=ns_addrs_txt
+noglue.example.com. 3600 IN A 192.0.2.53
+glue.delegation.example.com. 3600 IN A 192.0.2.153
+glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
+
+;var=
+glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=delegation_txt
+delegation.example.com. 3600 IN NS glue.delegation.example.com.
+delegation.example.com. 3600 IN NS noglue.example.com.
+delegation.example.com. 3600 IN NS cname.example.com.
+delegation.example.com. 3600 IN NS example.org.
+
+;var=
+delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Borrowed from the RFC4035
+;var=delegation_ds_txt
+delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
+;var=mx_txt
+mx.example.com. 3600 IN MX 10 www.example.com.
+mx.example.com. 3600 IN MX 20 mailer.example.org.
+mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
+;var=www_a_txt
+www.example.com. 3600 IN A 192.0.2.80
+
+;var=
+www.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cname_txt
+cname.example.com. 3600 IN CNAME www.example.com.
+;var=cname_nxdom_txt
+cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
+;; CNAME Leading out of zone
+;var=cname_out_txt
+cnameout.example.com. 3600 IN CNAME www.example.org.
+;; The DNAME to do tests against
+;var=dname_txt
+dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
+;; Some data at the dname node (allowed by RFC 2672)
+;var=dname_a_txt
+dname.example.com. 3600 IN A 192.0.2.5
+;; This is not inside the zone, this is created at runtime
+;; www.dname.example.com. 3600 IN CNAME www.somethinglong.dnametarget.example.com.
+;; The rest of data won't be referenced from the test cases.
+;var=other_zone_rrs
+cnamemailer.example.com. 3600 IN CNAME www.example.com.
+cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
+mx.delegation.example.com. 3600 IN A 192.0.2.100
+;; Wildcards
+;var=wild_txt
+*.wild.example.com. 3600 IN A 192.0.2.7
+;var=nsec_wild_txt
+*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
+
+;var=
+*.wild.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.wild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cnamewild_txt
+*.cnamewild.example.com. 3600 IN CNAME www.example.org.
+;var=nsec_cnamewild_txt
+*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
+
+;var=
+*.cnamewild.example.com. 3600 IN RRSIG CNAME 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.cnamewild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Wildcard_nxrrset
+;var=wild_txt_nxrrset
+*.uwild.example.com. 3600 IN A 192.0.2.9
+;var=nsec_wild_txt_nxrrset
+*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
+;var=
+*.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=wild_txt_next
+www.uwild.example.com. 3600 IN A 192.0.2.11
+;var=
+www.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_wild_txt_next
+www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
+;; Wildcard empty
+;var=empty_txt
+b.*.t.example.com. 3600 IN A 192.0.2.13
+;var=nsec_empty_txt
+b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
+
+;var=
+b.*.t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=empty_prev_txt
+t.example.com. 3600 IN A 192.0.2.15
+;var=nsec_empty_prev_txt
+t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
+
+;var=
+t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Used in NXDOMAIN proof test. We are going to test some unusual case where
+;; the best possible wildcard is below the "next domain" of the NSEC RR that
+;; proves the NXDOMAIN, i.e.,
+;; mx.example.com. (exist)
+;; (.no.example.com. (qname, NXDOMAIN)
+;; ).no.example.com. (exist)
+;; *.no.example.com. (best possible wildcard, not exist)
+;var=no_txt
+\).no.example.com. 3600 IN AAAA 2001:db8::53
+;; NSEC records.
+;var=nsec_apex_txt
+example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
+;var=
+example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_mx_txt
+mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+
+;var=
+mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=nsec_no_txt
+\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+
+;var=
+\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
+;; non existence of wildcard. The following records will be used for that
+;; test.
+;; ).no.example.com. (exist, whose NSEC proves everything)
+;; *.no.example.com. (best possible wildcard, not exist)
+;; nx.no.example.com. (NXDOMAIN)
+;; nz.no.example.com. (exist)
+;var=nz_txt
+nz.no.example.com. 3600 IN AAAA 2001:db8::5300
+;var=nsec_nz_txt
+nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
+;var=nsec_nxdomain_txt
+noglue.example.com. 3600 IN NSEC nonsec.example.com. A
+
+;var=
+noglue.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; NSEC for the normal NXRRSET case
+;var=nsec_www_txt
+www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
+
+;var=
+www.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Authoritative data without NSEC
+;var=nonsec_a_txt
+nonsec.example.com. 3600 IN A 192.0.2.0
+
+;; (Secure) delegation data; Delegation with DS record
+;var=signed_delegation_txt
+signed-delegation.example.com. 3600 IN NS ns.example.net.
+;var=signed_delegation_ds_txt
+signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
+
+;var=
+signed-delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; (Secure) delegation data; Delegation without DS record (and both NSEC
+;; and NSEC3 denying its existence)
+;var=unsigned_delegation_txt
+unsigned-delegation.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_nsec_txt
+unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
+
+;var=
+unsigned-delegation.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Delegation without DS record, and no direct matching NSEC3 record
+;var=unsigned_delegation_optout_txt
+unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_optout_nsec_txt
+unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
+
+;; (Secure) delegation data; Delegation where the DS lookup will raise an
+;; exception.
+;var=bad_delegation_txt
+bad-delegation.example.com. 3600 IN NS ns.example.net.
+
+;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
+;; or NSEC3 that proves it.
+;var=nosec_delegation_txt
+nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example-base.zone.in b/src/bin/auth/tests/testdata/example-base.zone.in
new file mode 100644
index 0000000..63d2af0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base.zone.in
@@ -0,0 +1,7 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests.
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example-common-inc-template.zone b/src/bin/auth/tests/testdata/example-common-inc-template.zone
new file mode 100644
index 0000000..d7259bf
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-common-inc-template.zone
@@ -0,0 +1,5 @@
+;;
+;; This is an initial template of part of test zone file used in query test
+;; and expected to be included from other zone files. This is
+;; intentionally kept empty.
+;;
diff --git a/src/bin/auth/tests/testdata/example-nsec3-inc.zone b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
new file mode 100644
index 0000000..7742df0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
@@ -0,0 +1,16 @@
+;; See query_testzone_data.txt for general notes.
+
+;; NSEC3PARAM. This is needed for database-based data source to
+;; signal the zone is NSEC3-signed
+;var=
+example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
+
+;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
+;var=nsec3_apex_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
+;var=nsec3_apex_rrsig_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec3_www_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+;var=nsec3_www_rrsig_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
diff --git a/src/bin/auth/tests/testdata/example-nsec3.zone.in b/src/bin/auth/tests/testdata/example-nsec3.zone.in
new file mode 100644
index 0000000..aaf3d6a
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3.zone.in
@@ -0,0 +1,8 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests including NSEC3 records, making the zone is "NSEC3 signed".
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_srcdir@/example-nsec3-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone
deleted file mode 100644
index efbbaf2..0000000
--- a/src/bin/auth/tests/testdata/example.zone
+++ /dev/null
@@ -1,121 +0,0 @@
-;;
-;; This is a complete (but crafted and somewhat broken) zone file used
-;; in query tests.
-;;
-
-example.com. 3600 IN SOA . . 0 0 0 0 0
-example.com. 3600 IN NS glue.delegation.example.com.
-example.com. 3600 IN NS noglue.example.com.
-example.com. 3600 IN NS example.net.
-example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-glue.delegation.example.com. 3600 IN A 192.0.2.153
-glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
-noglue.example.com. 3600 IN A 192.0.2.53
-delegation.example.com. 3600 IN NS glue.delegation.example.com.
-delegation.example.com. 3600 IN NS noglue.example.com.
-delegation.example.com. 3600 IN NS cname.example.com.
-delegation.example.com. 3600 IN NS example.org.
-;; Borrowed from the RFC4035
-delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-mx.example.com. 3600 IN MX 10 www.example.com.
-mx.example.com. 3600 IN MX 20 mailer.example.org.
-mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
-www.example.com. 3600 IN A 192.0.2.80
-cname.example.com. 3600 IN CNAME www.example.com.
-cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
-;; CNAME Leading out of zone
-cnameout.example.com. 3600 IN CNAME www.example.org.
-;; The DNAME to do tests against
-dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
-;; Some data at the dname node (allowed by RFC 2672)
-dname.example.com. 3600 IN A 192.0.2.5
-;; The rest of data won't be referenced from the test cases.
-cnamemailer.example.com. 3600 IN CNAME www.example.com.
-cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
-mx.delegation.example.com. 3600 IN A 192.0.2.100
-;; Wildcards
-*.wild.example.com. 3600 IN A 192.0.2.7
-*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
-*.cnamewild.example.com. 3600 IN CNAME www.example.org.
-*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
-;; Wildcard_nxrrset
-*.uwild.example.com. 3600 IN A 192.0.2.9
-*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
-www.uwild.example.com. 3600 IN A 192.0.2.11
-www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
-;; Wildcard empty
-b.*.t.example.com. 3600 IN A 192.0.2.13
-b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
-t.example.com. 3600 IN A 192.0.2.15
-t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
-;; Used in NXDOMAIN proof test. We are going to test some unusual case where
-;; the best possible wildcard is below the "next domain" of the NSEC RR that
-;; proves the NXDOMAIN, i.e.,
-;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
-;; *.no.example.com. (best possible wildcard, not exist)
-).no.example.com. 3600 IN AAAA 2001:db8::53
-;; NSEC records.
-example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
-mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG
-).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
-;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
-;; non existence of wildcard. The following records will be used for that
-;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
-;; *.no.example.com. (best possible wildcard, not exist)
-;; nx.no.example.com. (NXDOMAIN)
-;; nz.no.example.com. (exist)
-nz.no.example.com. 3600 IN AAAA 2001:db8::5300
-nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
-noglue.example.com. 3600 IN NSEC nonsec.example.com. A
-
-;; NSEC for the normal NXRRSET case
-www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
-
-;; Authoritative data without NSEC
-nonsec.example.com. 3600 IN A 192.0.2.0
-
-;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-
-;; NSEC3 for wild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse
-;; other tests)
-b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-;; NSEC3 for uwild.example.com. (will be added on demand)
-t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-
-;; (Secure) delegation data; Delegation with DS record
-signed-delegation.example.com. 3600 IN NS ns.example.net.
-signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
-
-;; (Secure) delegation data; Delegation without DS record (and both NSEC
-;; and NSEC3 denying its existence)
-unsigned-delegation.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
-;; This one will be added on demand
-q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG
-
-;; Delegation without DS record, and no direct matching NSEC3 record
-unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
-
-;; (Secure) delegation data; Delegation where the DS lookup will raise an
-;; exception.
-bad-delegation.example.com. 3600 IN NS ns.example.net.
-
-;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
-;; or NSEC3 that proves it.
-nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example.zone.in b/src/bin/auth/tests/testdata/example.zone.in
new file mode 100644
index 0000000..608c09f
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example.zone.in
@@ -0,0 +1,6 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests, excluding NSEC3 records.
+;;
+
+$INCLUDE @abs_builddir@/example-base.zone
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index 2ce86ab..e32544a 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -160,7 +160,7 @@
<citerefentry><refentrytitle>b10-msgq</refentrytitle><manvolnum>8</manvolnum></citerefentry>
daemon to use.
The default is
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+ <filename>/usr/local/var/bind10/msg_socket</filename>.
<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
</para>
</listitem>
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index aba8da5..9414ed6 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -154,7 +154,7 @@ The boss module received the given signal.
% BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
The boss module tried to restart a component after it failed (crashed)
unexpectedly, but the boss then found that the component had been removed
-from its local configuration of components to run. This is an unusal
+from its local configuration of components to run. This is an unusual
situation but can happen if the administrator removes the component from
the configuration after the component's crash and before the restart time.
The boss module simply skipped restarting that module, and the whole system
@@ -262,7 +262,7 @@ indicated OS API function with given error.
The boss forwards a request for a socket to the socket creator.
% BIND10_STARTED_CC started configuration/command session
-Debug message given when BIND 10 has successfull started the object that
+Debug message given when BIND 10 has successfully started the object that
handles configuration and commands.
% BIND10_STARTED_PROCESS started %1
@@ -308,13 +308,6 @@ During the startup process, a number of messages are exchanged between the
Boss process and the processes it starts. This error is output when a
message received by the Boss process is not recognised.
-% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
-The resolver is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
% BIND10_STOP_PROCESS asking %1 to shut down
The boss module is sending a shutdown command to the given module over
the message channel.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 36ad760..6fe5485 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -48,7 +48,7 @@ else:
PREFIX = "@prefix@"
DATAROOTDIR = "@datarootdir@"
SPECFILE_LOCATION = "@datadir@/@PACKAGE@/bob.spec".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
-
+
import subprocess
import signal
import re
@@ -76,7 +76,7 @@ import isc.bind10.socket_cache
import libutil_io_python
import tempfile
-isc.log.init("b10-boss")
+isc.log.init("b10-boss", buffer=True)
logger = isc.log.Logger("boss")
# Pending system-wide debug level definitions, the ones we
@@ -103,8 +103,31 @@ VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for boot_time of Boss
_BASETIME = time.gmtime()
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file. We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
class ProcessInfoError(Exception): pass
+class ChangeUserError(Exception):
+ '''Exception raised when setuid/setgid fails.
+
+ When raised, it's expected to be propagated via underlying component
+ management modules to the top level so that it will help provide useful
+ fatal error message.
+
+ '''
+ pass
+
class ProcessInfo:
"""Information about a process"""
@@ -166,14 +189,14 @@ class ProcessStartError(Exception): pass
class BoB:
"""Boss of BIND class."""
-
+
def __init__(self, msgq_socket_file=None, data_path=None,
config_filename=None, clear_config=False,
verbose=False, nokill=False, setuid=None, setgid=None,
username=None, cmdctl_port=None, wait_time=10):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
-
+
The msgq_socket_file specifies the UNIX domain socket file that the
msgq process listens on. If verbose is True, then the boss reports
what it is doing.
@@ -206,8 +229,8 @@ class BoB:
# restart. Components manage their own restart schedule now
self.components_to_restart = []
self.runnable = False
- self.uid = setuid
- self.gid = setgid
+ self.__uid = setuid
+ self.__gid = setgid
self.username = username
self.verbose = verbose
self.nokill = nokill
@@ -216,6 +239,12 @@ class BoB:
self.clear_config = clear_config
self.cmdctl_port = cmdctl_port
self.wait_time = wait_time
+ self.msgq_timeout = 5
+
+ # _run_under_unittests is only meant to be used when testing. It
+ # bypasses execution of some code to help with testing.
+ self._run_under_unittests = False
+
self._component_configurator = isc.bind10.component.Configurator(self,
isc.bind10.special_component.get_specials())
# The priorities here make them start in the correct order. First
@@ -263,6 +292,31 @@ class BoB:
# Update the configuration
self._component_configurator.reconfigure(comps)
+ def change_user(self):
+ '''Change the user and group to those specified on construction.
+
+ This method is expected to be called by a component on initial
+ startup when the system is ready to switch the user and group
+ (i.e., once all components that need the privilege of the original
+ user have started).
+ '''
+ try:
+ if self.__gid is not None:
+ logger.info(BIND10_SETGID, self.__gid)
+ posix.setgid(self.__gid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change group: ' + str(ex))
+
+ try:
+ if self.__uid is not None:
+ posix.setuid(self.__uid)
+ # We use one-shot logger after setuid here. This will
+ # detect any permission issue regarding logging due to the
+ # result of setuid at the earliest opportunity.
+ isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change user: ' + str(ex))
+
def config_handler(self, new_config):
# If this is initial update, don't do anything now, leave it to startup
if not self.runnable:
@@ -332,6 +386,7 @@ class BoB:
"""
logger.info(BIND10_KILLING_ALL_PROCESSES)
self.__kill_children(True)
+ self.components = {}
def _read_bind10_config(self):
"""
@@ -400,7 +455,7 @@ class BoB:
logger.error(BIND10_STARTUP_UNEXPECTED_MESSAGE, msg)
except:
logger.error(BIND10_STARTUP_UNRECOGNISED_MESSAGE, msg)
-
+
return False
# The next few methods start the individual processes of BIND-10. They
@@ -408,21 +463,34 @@ class BoB:
# raised which is caught by the caller of start_all_processes(); this kills
# processes started up to that point before terminating the program.
+ def _make_process_info(self, name, args, env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ """
+ Wrapper around ProcessInfo(), useful to override
+ ProcessInfo() creation during testing.
+ """
+ return ProcessInfo(name, args, env, dev_null_stdout, dev_null_stderr)
+
def start_msgq(self):
"""
Start the message queue and connect to the command channel.
"""
self.log_starting("b10-msgq")
- msgq_proc = ProcessInfo("b10-msgq", ["b10-msgq"], self.c_channel_env,
- True, not self.verbose)
+ msgq_proc = self._make_process_info("b10-msgq", ["b10-msgq"],
+ self.c_channel_env,
+ True, not self.verbose)
msgq_proc.spawn()
self.log_started(msgq_proc.pid)
# Now connect to the c-channel
cc_connect_start = time.time()
while self.cc_session is None:
+ # if we are run under unittests, break
+ if self._run_under_unittests:
+ break
+
# if we have been trying for "a while" give up
- if (time.time() - cc_connect_start) > 5:
+ if (time.time() - cc_connect_start) > self.msgq_timeout:
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
@@ -434,7 +502,8 @@ class BoB:
# Subscribe to the message queue. The only messages we expect to receive
# on this channel are once relating to process startup.
- self.cc_session.group_subscribe("Boss")
+ if self.cc_session is not None:
+ self.cc_session.group_subscribe("Boss")
return msgq_proc
@@ -450,13 +519,14 @@ class BoB:
args.append("--config-filename=" + self.config_filename)
if self.clear_config:
args.append("--clear-config")
- bind_cfgd = ProcessInfo("b10-cfgmgr", args,
- self.c_channel_env)
+ bind_cfgd = self._make_process_info("b10-cfgmgr", args,
+ self.c_channel_env)
bind_cfgd.spawn()
self.log_started(bind_cfgd.pid)
- # Wait for the configuration manager to start up as subsequent initialization
- # cannot proceed without it. The time to wait can be set on the command line.
+ # Wait for the configuration manager to start up as
+ # subsequent initialization cannot proceed without it. The
+ # time to wait can be set on the command line.
time_remaining = self.wait_time
msg, env = self.cc_session.group_recvmsg()
while time_remaining > 0 and not self.process_running(msg, "ConfigManager"):
@@ -464,7 +534,7 @@ class BoB:
time.sleep(1)
time_remaining = time_remaining - 1
msg, env = self.cc_session.group_recvmsg()
-
+
if not self.process_running(msg, "ConfigManager"):
raise ProcessStartError("Configuration manager process has not started")
@@ -481,7 +551,7 @@ class BoB:
process, the log_starting/log_started methods are not used.
"""
logger.info(BIND10_STARTING_CC)
- self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+ self.ccs = isc.config.ModuleCCSession(SPECFILE_LOCATION,
self.config_handler,
self.command_handler,
socket_file = self.msgq_socket_file)
@@ -499,7 +569,7 @@ class BoB:
The port and address arguments are for log messages only.
"""
self.log_starting(name, port, address)
- newproc = ProcessInfo(name, args, c_channel_env)
+ newproc = self._make_process_info(name, args, c_channel_env)
newproc.spawn()
self.log_started(newproc.pid)
return newproc
@@ -556,8 +626,6 @@ class BoB:
are pure speculation. As with the auth daemon, they should be
read from the configuration database.
"""
- if self.uid is not None and self.__started:
- logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
self.curproc = "b10-resolver"
# XXX: this must be read from the configuration manager in the future
resargs = ['b10-resolver']
@@ -611,7 +679,6 @@ class BoB:
if self.msgq_socket_file is not None:
c_channel_env["BIND10_MSGQ_SOCKET_FILE"] = self.msgq_socket_file
logger.debug(DBG_PROCESS, BIND10_CHECK_MSGQ_ALREADY_RUNNING)
- # try to connect, and if we can't wait a short while
try:
self.cc_session = isc.cc.Session(self.msgq_socket_file)
logger.fatal(BIND10_MSGQ_ALREADY_RUNNING)
@@ -625,6 +692,9 @@ class BoB:
try:
self.c_channel_env = c_channel_env
self.start_all_components()
+ except ChangeUserError as e:
+ self.kill_started_components()
+ return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
except Exception as e:
self.kill_started_components()
return "Unable to start " + self.curproc + ": " + str(e)
@@ -679,7 +749,7 @@ class BoB:
except:
pass
# XXX: some delay probably useful... how much is uncertain
- # I have changed the delay from 0.5 to 1, but sometime it's
+ # I have changed the delay from 0.5 to 1, but sometime it's
# still not enough.
time.sleep(1)
self.reap_children()
@@ -728,17 +798,19 @@ class BoB:
return os.waitpid(-1, os.WNOHANG)
def reap_children(self):
- """Check to see if any of our child processes have exited,
- and note this for later handling.
+ """Check to see if any of our child processes have exited,
+ and note this for later handling.
"""
while True:
try:
(pid, exit_status) = self._get_process_exit_status()
except OSError as o:
- if o.errno == errno.ECHILD: break
+ if o.errno == errno.ECHILD:
+ break
# XXX: should be impossible to get any other error here
raise
- if pid == 0: break
+ if pid == 0:
+ break
if pid in self.components:
# One of the components we know about. Get information on it.
component = self.components.pop(pid)
@@ -760,11 +832,11 @@ class BoB:
"""
Restart any dead processes:
- * Returns the time when the next process is ready to be restarted.
+ * Returns the time when the next process is ready to be restarted.
* If the server is shutting down, returns 0.
* If there are no processes, returns None.
- The values returned can be safely passed into select() as the
+ The values returned can be safely passed into select() as the
timeout value.
"""
@@ -909,8 +981,10 @@ class BoB:
"""
if self._srv_socket is not None:
self._srv_socket.close()
- os.remove(self._socket_path)
- os.rmdir(self._tmpdir)
+ if os.path.exists(self._socket_path):
+ os.remove(self._socket_path)
+ if os.path.isdir(self._tmpdir):
+ os.rmdir(self._tmpdir)
def _srv_accept(self):
"""
@@ -1006,7 +1080,7 @@ boss_of_bind = None
def reaper(signal_number, stack_frame):
"""A child process has died (SIGCHLD received)."""
- # don't do anything...
+ # don't do anything...
# the Python signal handler has been set up to write
# down a pipe, waking up our select() bit
pass
@@ -1130,7 +1204,19 @@ def remove_lock_files():
for f in lockfiles:
fname = lpath + '/' + f
if os.path.isfile(fname):
- os.unlink(fname)
+ try:
+ os.unlink(fname)
+ except OSError as e:
+ # We catch and ignore permission related error on unlink.
+ # This can happen if bind10 started with -u, created a lock
+ # file as a privileged user, but the directory is not writable
+ # for the changed user. This setup will cause immediate
+ # start failure, and we leave verbose error message including
+ # the leftover lock file, so it should be acceptable to ignore
+ # it (note that it doesn't make sense to log this event at
+ # this poitn)
+ if e.errno != errno.EPERM and e.errno != errno.EACCES:
+ raise
return
@@ -1148,13 +1234,7 @@ def main():
except RuntimeError as e:
sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
str(e))
- sys.stderr.write("""\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user. Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user.
-""")
+ sys.stderr.write(NOTE_ON_LOCK_FILE)
sys.exit(1)
# Check user ID.
@@ -1173,7 +1253,7 @@ and the created lock file must be writable for that user.
except KeyError:
pass
- # Next try getting information about the user, assuming user name
+ # Next try getting information about the user, assuming user name
# passed.
# If the information is both a valid user name and user number, we
# prefer the name because we try it second. A minor point, hopefully.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index ece6370..ccfa831 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -25,6 +25,7 @@ import bind10_src
import unittest
import sys
import os
+import os.path
import copy
import signal
import socket
@@ -34,6 +35,7 @@ import isc
import isc.log
import isc.bind10.socket_cache
import errno
+import random
from isc.testutils.parse_args import TestOptParser, OptsError
from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -339,6 +341,18 @@ class TestCacheCommands(unittest.TestCase):
class TestBoB(unittest.TestCase):
+ def setUp(self):
+ # Save original values that may be tweaked in some tests
+ self.__orig_setgid = bind10_src.posix.setgid
+ self.__orig_setuid = bind10_src.posix.setuid
+ self.__orig_logger_class = isc.log.Logger
+
+ def tearDown(self):
+ # Restore original values saved in setUp()
+ bind10_src.posix.setgid = self.__orig_setgid
+ bind10_src.posix.setuid = self.__orig_setuid
+ isc.log.Logger = self.__orig_logger_class
+
def test_init(self):
bob = BoB()
self.assertEqual(bob.verbose, False)
@@ -347,10 +361,56 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
self.assertIsNone(bob._socket_cache)
+ def __setgid(self, gid):
+ self.__gid_set = gid
+
+ def __setuid(self, uid):
+ self.__uid_set = uid
+
+ def test_change_user(self):
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+
+ self.__gid_set = None
+ self.__uid_set = None
+ bob = BoB()
+ bob.change_user()
+ # No gid/uid set in boss, nothing called.
+ self.assertIsNone(self.__gid_set)
+ self.assertIsNone(self.__uid_set)
+
+ BoB(setuid=42, setgid=4200).change_user()
+ # This time, it get's called
+ self.assertEqual(4200, self.__gid_set)
+ self.assertEqual(42, self.__uid_set)
+
+ def raising_set_xid(gid_or_uid):
+ ex = OSError()
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ raise ex
+
+ # Let setgid raise an exception
+ bind10_src.posix.setgid = raising_set_xid
+ bind10_src.posix.setuid = self.__setuid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let initial log output after setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+ isc.log.Logger = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
def test_set_creator(self):
"""
Test the call to set_creator. First time, the cache is created
@@ -366,6 +426,53 @@ class TestBoB(unittest.TestCase):
self.assertEqual(creator, bob._socket_cache._creator)
self.assertRaises(ValueError, bob.set_creator, creator)
+ def test_socket_srv(self):
+ """Tests init_socket_srv() and remove_socket_srv() work as expected."""
+ bob = BoB()
+
+ self.assertIsNone(bob._srv_socket)
+ self.assertIsNone(bob._tmpdir)
+ self.assertIsNone(bob._socket_path)
+
+ bob.init_socket_srv()
+
+ self.assertIsNotNone(bob._srv_socket)
+ self.assertNotEqual(-1, bob._srv_socket.fileno())
+ self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'),
+ bob._srv_socket.getsockname())
+
+ self.assertIsNotNone(bob._tmpdir)
+ self.assertTrue(os.path.isdir(bob._tmpdir))
+ self.assertIsNotNone(bob._socket_path)
+ self.assertTrue(os.path.exists(bob._socket_path))
+
+ # Check that it's possible to connect to the socket file (this
+ # only works if the socket file exists and the server listens on
+ # it).
+ s = socket.socket(socket.AF_UNIX)
+ try:
+ s.connect(bob._socket_path)
+ can_connect = True
+ s.close()
+ except socket.error as e:
+ can_connect = False
+
+ self.assertTrue(can_connect)
+
+ bob.remove_socket_srv()
+
+ self.assertEqual(-1, bob._srv_socket.fileno())
+ self.assertFalse(os.path.exists(bob._socket_path))
+ self.assertFalse(os.path.isdir(bob._tmpdir))
+
+ # These should not fail either:
+
+ # second call
+ bob.remove_socket_srv()
+
+ bob._srv_socket = None
+ bob.remove_socket_srv()
+
def test_init_alternate_socket(self):
bob = BoB("alt_socket_file")
self.assertEqual(bob.verbose, False)
@@ -374,7 +481,6 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
def test_command_handler(self):
@@ -461,6 +567,22 @@ class TestBoB(unittest.TestCase):
self.assertEqual({'command': ['shutdown', {'pid': 42}]},
bob.cc_session.msg)
+# Mock class for testing BoB's usage of ProcessInfo
+class MockProcessInfo:
+ def __init__(self, name, args, env={}, dev_null_stdout=False,
+ dev_null_stderr=False):
+ self.name = name
+ self.args = args
+ self.env = env
+ self.dev_null_stdout = dev_null_stdout
+ self.dev_null_stderr = dev_null_stderr
+ self.process = None
+ self.pid = None
+
+ def spawn(self):
+ # set some pid (only used for testing that it is not None anymore)
+ self.pid = 42147
+
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
@@ -490,6 +612,7 @@ class MockBob(BoB):
self.c_channel_env = {}
self.components = { }
self.creator = False
+ self.get_process_exit_status_called = False
class MockSockCreator(isc.bind10.component.Component):
def __init__(self, process, boss, kind, address=None, params=None):
@@ -661,6 +784,52 @@ class MockBob(BoB):
del self.components[12]
self.cmdctl = False
+ def _get_process_exit_status(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (53, 0)
+
+ def _get_process_exit_status_unknown_pid(self):
+ if self.get_process_exit_status_called:
+ return (0, 0)
+ self.get_process_exit_status_called = True
+ return (42, 0)
+
+ def _get_process_exit_status_raises_oserror_echild(self):
+ raise OSError(errno.ECHILD, 'Mock error')
+
+ def _get_process_exit_status_raises_oserror_other(self):
+ raise OSError(0, 'Mock error')
+
+ def _get_process_exit_status_raises_other(self):
+ raise Exception('Mock error')
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+class MockBobSimple(BoB):
+ def __init__(self):
+ BoB.__init__(self)
+ # Set which process has been started
+ self.started_process_name = None
+ self.started_process_args = None
+ self.started_process_env = None
+
+ def _make_mock_process_info(self, name, args, c_channel_env,
+ dev_null_stdout=False, dev_null_stderr=False):
+ return MockProcessInfo(name, args, c_channel_env,
+ dev_null_stdout, dev_null_stderr)
+
+ def start_process(self, name, args, c_channel_env, port=None,
+ address=None):
+ self.started_process_name = name
+ self.started_process_args = args
+ self.started_process_env = c_channel_env
+ return None
+
class TestStartStopProcessesBob(unittest.TestCase):
"""
Check that the start_all_components method starts the right combination
@@ -930,6 +1099,9 @@ class MockComponent:
self.pid = lambda: pid
self.address = lambda: address
self.restarted = False
+ self.forceful = False
+ self.running = True
+ self.has_failed = False
def get_restart_time(self):
return 0 # arbitrary dummy value
@@ -938,6 +1110,15 @@ class MockComponent:
self.restarted = True
return True
+ def is_running(self):
+ return self.running
+
+ def failed(self, status):
+ return self.has_failed
+
+ def kill(self, forceful):
+ self.forceful = forceful
+
class TestBossCmd(unittest.TestCase):
def test_ping(self):
"""
@@ -1107,6 +1288,20 @@ class TestBossComponents(unittest.TestCase):
'process': 'cat'
}
}
+ self._tmp_time = None
+ self._tmp_sleep = None
+ self._tmp_module_cc_session = None
+ self._tmp_cc_session = None
+
+ def tearDown(self):
+ if self._tmp_time is not None:
+ time.time = self._tmp_time
+ if self._tmp_sleep is not None:
+ time.sleep = self._tmp_sleep
+ if self._tmp_module_cc_session is not None:
+ isc.config.ModuleCCSession = self._tmp_module_cc_session
+ if self._tmp_cc_session is not None:
+ isc.cc.Session = self._tmp_cc_session
def __unary_hook(self, param):
"""
@@ -1324,14 +1519,626 @@ class TestBossComponents(unittest.TestCase):
bob._component_configurator._components['test'] = (None, component)
self.__setup_restart(bob, component)
self.assertTrue(component.restarted)
- self.assertFalse(component in bob.components_to_restart)
+ self.assertNotIn(component, bob.components_to_restart)
# Remove the component from the configuration. It won't be restarted
# even if scheduled, nor will remain in the to-be-restarted list.
del bob._component_configurator._components['test']
self.__setup_restart(bob, component)
self.assertFalse(component.restarted)
- self.assertFalse(component in bob.components_to_restart)
+ self.assertNotIn(component, bob.components_to_restart)
+
+ def test_get_processes(self):
+ '''Test that procsses are returned correctly, sorted by pid.'''
+ bob = MockBob()
+
+ pids = list(range(0, 20))
+ random.shuffle(pids)
+
+ for i in range(0, 20):
+ pid = pids[i]
+ component = MockComponent('test' + str(pid), pid,
+ 'Test' + str(pid))
+ bob.components[pid] = component
+
+ process_list = bob.get_processes()
+ self.assertEqual(20, len(process_list))
+
+ last_pid = -1
+ for process in process_list:
+ pid = process[0]
+ self.assertLessEqual(last_pid, pid)
+ last_pid = pid
+ self.assertEqual([pid, 'test' + str(pid), 'Test' + str(pid)],
+ process)
+
+ def _test_reap_children_helper(self, runnable, is_running, failed):
+ '''Construct a BoB instance, set various data in it according to
+ passed args and check if the component was added to the list of
+ components to restart.'''
+ bob = MockBob()
+ bob.runnable = runnable
+
+ component = MockComponent('test', 53)
+ component.running = is_running
+ component.has_failed = failed
+ bob.components[53] = component
+
+ self.assertNotIn(component, bob.components_to_restart)
+
+ bob.reap_children()
+
+ if runnable and is_running and not failed:
+ self.assertIn(component, bob.components_to_restart)
+ else:
+ self.assertEqual([], bob.components_to_restart)
+
+ def test_reap_children(self):
+ '''Test that children are queued to be restarted when they ask for it.'''
+ # test various combinations of 3 booleans
+ # (BoB.runnable, component.is_running(), component.failed())
+ self._test_reap_children_helper(False, False, False)
+ self._test_reap_children_helper(False, False, True)
+ self._test_reap_children_helper(False, True, False)
+ self._test_reap_children_helper(False, True, True)
+ self._test_reap_children_helper(True, False, False)
+ self._test_reap_children_helper(True, False, True)
+ self._test_reap_children_helper(True, True, False)
+ self._test_reap_children_helper(True, True, True)
+
+ # setup for more tests below
+ bob = MockBob()
+ bob.runnable = True
+ component = MockComponent('test', 53)
+ bob.components[53] = component
+
+ # case where the returned pid is unknown to us. nothing should
+ # happpen then.
+ bob.get_process_exit_status_called = False
+ bob._get_process_exit_status = bob._get_process_exit_status_unknown_pid
+ bob.components_to_restart = []
+ # this should do nothing as the pid is unknown
+ bob.reap_children()
+ self.assertEqual([], bob.components_to_restart)
+
+ # case where bob._get_process_exit_status() raises OSError with
+ # errno.ECHILD
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_oserror_echild
+ bob.components_to_restart = []
+ # this should catch and handle the OSError
+ bob.reap_children()
+ self.assertEqual([], bob.components_to_restart)
+
+ # case where bob._get_process_exit_status() raises OSError with
+ # errno other than ECHILD
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_oserror_other
+ with self.assertRaises(OSError):
+ bob.reap_children()
+
+ # case where bob._get_process_exit_status() raises something
+ # other than OSError
+ bob._get_process_exit_status = \
+ bob._get_process_exit_status_raises_other
+ with self.assertRaises(Exception):
+ bob.reap_children()
+
+ def test_kill_started_components(self):
+ '''Test that started components are killed.'''
+ bob = MockBob()
+
+ component = MockComponent('test', 53, 'Test')
+ bob.components[53] = component
+
+ self.assertEqual([[53, 'test', 'Test']], bob.get_processes())
+ bob.kill_started_components()
+ self.assertEqual([], bob.get_processes())
+ self.assertTrue(component.forceful)
+
+ def _start_msgq_helper(self, bob, verbose):
+ bob.verbose = verbose
+ pi = bob.start_msgq()
+ self.assertEqual('b10-msgq', pi.name)
+ self.assertEqual(['b10-msgq'], pi.args)
+ self.assertTrue(pi.dev_null_stdout)
+ self.assertEqual(pi.dev_null_stderr, not verbose)
+ self.assertEqual({'FOO': 'an env string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_msgq(self):
+ '''Test that b10-msgq is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'FOO': 'an env string'}
+ bob._run_under_unittests = True
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ # non-verbose case
+ self._start_msgq_helper(bob, False)
+
+ # verbose case
+ self._start_msgq_helper(bob, True)
+
+ def test_start_msgq_timeout(self):
+ '''Test that b10-msgq startup attempts connections several times
+ and times out eventually.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {}
+ # set the timeout to an arbitrary pre-determined value (which
+ # code below depends on)
+ bob.msgq_timeout = 1
+ bob._run_under_unittests = False
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ global tsec
+ attempts = 0
+ tsec = 0
+ self._tmp_time = time.time
+ self._tmp_sleep = time.sleep
+ def _my_time():
+ global attempts
+ global tsec
+ attempts += 1
+ return tsec
+ def _my_sleep(nsec):
+ global tsec
+ tsec += nsec
+ time.time = _my_time
+ time.sleep = _my_sleep
+
+ global cc_sub
+ cc_sub = None
+ class DummySessionAlwaysFails():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('Connection fails')
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySessionAlwaysFails
+
+ with self.assertRaises(bind10_src.CChannelConnectError):
+ # An exception will be thrown here when it eventually times
+ # out.
+ pi = bob.start_msgq()
+
+ # time.time() should be called 12 times within the while loop:
+ # starting from 0, and 11 more times from 0.1 to 1.1. There's
+ # another call to time.time() outside the loop, which makes it
+ # 13.
+ self.assertEqual(attempts, 13)
+
+ # group_subscribe() should not have been called here.
+ self.assertIsNone(cc_sub)
+
+ global cc_socket_file
+ cc_socket_file = None
+ cc_sub = None
+ class DummySession():
+ def __init__(self, socket_file):
+ global cc_socket_file
+ cc_socket_file = socket_file
+ def group_subscribe(self, s):
+ global cc_sub
+ cc_sub = s
+
+ isc.cc.Session = DummySession
+
+ # reset values
+ attempts = 0
+ tsec = 0
+
+ pi = bob.start_msgq()
+
+ # just one attempt, but 2 calls to time.time()
+ self.assertEqual(attempts, 2)
+
+ self.assertEqual(cc_socket_file, bob.msgq_socket_file)
+ self.assertEqual(cc_sub, 'Boss')
+
+ # isc.cc.Session, time.time() and time.sleep() are restored
+ # during tearDown().
+
+ def _start_cfgmgr_helper(self, bob, data_path, filename, clear_config):
+ expect_args = ['b10-cfgmgr']
+ if data_path is not None:
+ bob.data_path = data_path
+ expect_args.append('--data-path=' + data_path)
+ if filename is not None:
+ bob.config_filename = filename
+ expect_args.append('--config-filename=' + filename)
+ if clear_config:
+ bob.clear_config = clear_config
+ expect_args.append('--clear-config')
+
+ pi = bob.start_cfgmgr()
+ self.assertEqual('b10-cfgmgr', pi.name)
+ self.assertEqual(expect_args, pi.args)
+ self.assertEqual({'TESTENV': 'A test string'}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_start_cfgmgr(self):
+ '''Test that b10-cfgmgr is started.'''
+ class DummySession():
+ def __init__(self):
+ self._tries = 0
+ def group_recvmsg(self):
+ self._tries += 1
+ # return running on the 3rd try onwards
+ if self._tries >= 3:
+ return ({'running': 'ConfigManager'}, None)
+ else:
+ return ({}, None)
+
+ bob = MockBobSimple()
+ bob.c_channel_env = {'TESTENV': 'A test string'}
+ bob.cc_session = DummySession()
+ bob.wait_time = 5
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # defaults
+ self._start_cfgmgr_helper(bob, None, None, False)
+
+ # check that 2 attempts were made. on the 3rd attempt,
+ # process_running() returns that ConfigManager is running.
+ self.assertEqual(attempts, 2)
+
+ # data_path is specified
+ self._start_cfgmgr_helper(bob, '/var/lib/test', None, False)
+
+ # config_filename is specified. Because `bob` is not
+ # reconstructed, data_path is retained from the last call to
+ # _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', False)
+
+ # clear_config is specified. Because `bob` is not reconstructed,
+ # data_path and config_filename are retained from the last call
+ # to _start_cfgmgr_helper().
+ self._start_cfgmgr_helper(bob, '/var/lib/test', 'foo.cfg', True)
+
+ def test_start_cfgmgr_timeout(self):
+ '''Test that b10-cfgmgr startup attempts connections several times
+ and times out eventually.'''
+ class DummySession():
+ def group_recvmsg(self):
+ return (None, None)
+ bob = MockBobSimple()
+ bob.c_channel_env = {}
+ bob.cc_session = DummySession()
+ # set wait_time to an arbitrary pre-determined value (which code
+ # below depends on)
+ bob.wait_time = 2
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ global attempts
+ attempts = 0
+ self._tmp_sleep = time.sleep
+ def _my_sleep(nsec):
+ global attempts
+ attempts += 1
+ time.sleep = _my_sleep
+
+ # We just check that an exception was thrown, and that several
+ # attempts were made to connect.
+ with self.assertRaises(bind10_src.ProcessStartError):
+ pi = bob.start_cfgmgr()
+
+ # 2 seconds of attempts every 1 second should result in 2 attempts
+ self.assertEqual(attempts, 2)
+
+ # time.sleep() is restored during tearDown().
+
+ def test_start_ccsession(self):
+ '''Test that CC session is started.'''
+ class DummySession():
+ def __init__(self, specfile, config_handler, command_handler,
+ socket_file):
+ self.specfile = specfile
+ self.config_handler = config_handler
+ self.command_handler = command_handler
+ self.socket_file = socket_file
+ self.started = False
+ def start(self):
+ self.started = True
+ bob = MockBobSimple()
+ self._tmp_module_cc_session = isc.config.ModuleCCSession
+ isc.config.ModuleCCSession = DummySession
+
+ bob.start_ccsession({})
+ self.assertEqual(bind10_src.SPECFILE_LOCATION, bob.ccs.specfile)
+ self.assertEqual(bob.config_handler, bob.ccs.config_handler)
+ self.assertEqual(bob.command_handler, bob.ccs.command_handler)
+ self.assertEqual(bob.msgq_socket_file, bob.ccs.socket_file)
+ self.assertTrue(bob.ccs.started)
+
+ # isc.config.ModuleCCSession is restored during tearDown().
+
+ def test_start_process(self):
+ '''Test that processes can be started.'''
+ bob = MockBob()
+
+ # use the MockProcessInfo creator
+ bob._make_process_info = bob._make_mock_process_info
+
+ pi = bob.start_process('Test Process', ['/bin/true'], {})
+ self.assertEqual('Test Process', pi.name)
+ self.assertEqual(['/bin/true'], pi.args)
+ self.assertEqual({}, pi.env)
+
+ # this is set by ProcessInfo.spawn()
+ self.assertEqual(42147, pi.pid)
+
+ def test_register_process(self):
+ '''Test that processes can be registered with BoB.'''
+ bob = MockBob()
+ component = MockComponent('test', 53, 'Test')
+
+ self.assertFalse(53 in bob.components)
+ bob.register_process(53, component)
+ self.assertTrue(53 in bob.components)
+ self.assertEqual(bob.components[53].name(), 'test')
+ self.assertEqual(bob.components[53].pid(), 53)
+ self.assertEqual(bob.components[53].address(), 'Test')
+
+ def _start_simple_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['/bin/true']
+ if verbose:
+ args.append('-v')
+
+ bob.start_simple('/bin/true')
+ self.assertEqual('/bin/true', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'TESTENV': 'A test string'}, bob.started_process_env)
+
+ def test_start_simple(self):
+ '''Test simple process startup.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'TESTENV': 'A test string'}
+
+ # non-verbose case
+ self._start_simple_helper(bob, False)
+
+ # verbose case
+ self._start_simple_helper(bob, True)
+
+ def _start_auth_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['b10-auth']
+ if verbose:
+ args.append('-v')
+
+ bob.start_auth()
+ self.assertEqual('b10-auth', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'FOO': 'an env string'}, bob.started_process_env)
+
+ def test_start_auth(self):
+ '''Test that b10-auth is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'FOO': 'an env string'}
+
+ # non-verbose case
+ self._start_auth_helper(bob, False)
+
+ # verbose case
+ self._start_auth_helper(bob, True)
+
+ def _start_resolver_helper(self, bob, verbose):
+ bob.verbose = verbose
+
+ args = ['b10-resolver']
+ if verbose:
+ args.append('-v')
+
+ bob.start_resolver()
+ self.assertEqual('b10-resolver', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'BAR': 'an env string'}, bob.started_process_env)
+
+ def test_start_resolver(self):
+ '''Test that b10-resolver is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'BAR': 'an env string'}
+
+ # non-verbose case
+ self._start_resolver_helper(bob, False)
+
+ # verbose case
+ self._start_resolver_helper(bob, True)
+
+ def _start_cmdctl_helper(self, bob, verbose, port = None):
+ bob.verbose = verbose
+
+ args = ['b10-cmdctl']
+
+ if port is not None:
+ bob.cmdctl_port = port
+ args.append('--port=9353')
+
+ if verbose:
+ args.append('-v')
+
+ bob.start_cmdctl()
+ self.assertEqual('b10-cmdctl', bob.started_process_name)
+ self.assertEqual(args, bob.started_process_args)
+ self.assertEqual({'BAZ': 'an env string'}, bob.started_process_env)
+
+ def test_start_cmdctl(self):
+ '''Test that b10-cmdctl is started.'''
+ bob = MockBobSimple()
+ bob.c_channel_env = {'BAZ': 'an env string'}
+
+ # non-verbose case
+ self._start_cmdctl_helper(bob, False)
+
+ # verbose case
+ self._start_cmdctl_helper(bob, True)
+
+ # with port, non-verbose case
+ self._start_cmdctl_helper(bob, False, 9353)
+
+ # with port, verbose case
+ self._start_cmdctl_helper(bob, True, 9353)
+
+ def test_socket_data(self):
+ '''Test that BoB._socket_data works as expected.'''
+ class MockSock:
+ def __init__(self, fd, throw):
+ self.fd = fd
+ self.throw = throw
+ self.buf = b'Hello World.\nYou are so nice today.\nXX'
+ self.i = 0
+
+ def recv(self, bufsize, flags = 0):
+ if bufsize != 1:
+ raise Exception('bufsize != 1')
+ if flags != socket.MSG_DONTWAIT:
+ raise Exception('flags != socket.MSG_DONTWAIT')
+ # after 15 recv()s, throw a socket.error with EAGAIN to
+ # get _socket_data() to save back what's been read. The
+ # number 15 is arbitrarily chosen, but the checks then
+ # depend on this being 15, i.e., if you adjust this
+ # number, you may have to adjust the checks below too.
+ if self.throw and self.i > 15:
+ raise socket.error(errno.EAGAIN, 'Try again')
+ if self.i >= len(self.buf):
+ return b'';
+ t = self.i
+ self.i += 1
+ return self.buf[t:t+1]
+
+ def close(self):
+ return
+
+ class MockBobSocketData(BoB):
+ def __init__(self, throw):
+ self._unix_sockets = {42: (MockSock(42, throw), b'')}
+ self.requests = []
+ self.dead = []
+
+ def socket_request_handler(self, previous, sock):
+ self.requests.append({sock.fd: previous})
+
+ def socket_consumer_dead(self, sock):
+ self.dead.append(sock.fd)
+
+ # Case where we get data every time we call recv()
+ bob = MockBobSocketData(False)
+ bob._socket_data(42)
+ self.assertEqual(bob.requests,
+ [{42: b'Hello World.'},
+ {42: b'You are so nice today.'}])
+ self.assertEqual(bob.dead, [42])
+ self.assertEqual({}, bob._unix_sockets)
+
+ # Case where socket.recv() raises EAGAIN. In this case, the
+ # routine is supposed to save what it has back to
+ # BoB._unix_sockets.
+ bob = MockBobSocketData(True)
+ bob._socket_data(42)
+ self.assertEqual(bob.requests, [{42: b'Hello World.'}])
+ self.assertFalse(bob.dead)
+ self.assertEqual(len(bob._unix_sockets), 1)
+ self.assertEqual(bob._unix_sockets[42][1], b'You')
+
+ def test_startup(self):
+ '''Test that BoB.startup() handles failures properly.'''
+ class MockBobStartup(BoB):
+ def __init__(self, throw):
+ self.throw = throw
+ self.started = False
+ self.killed = False
+ self.msgq_socket_file = None
+ self.curproc = 'myproc'
+ self.runnable = False
+
+ def start_all_components(self):
+ self.started = True
+ if self.throw is True:
+ raise Exception('Assume starting components has failed.')
+ elif self.throw:
+ raise self.throw
+
+ def kill_started_components(self):
+ self.killed = True
+
+ class DummySession():
+ def __init__(self, socket_file):
+ raise isc.cc.session.SessionError('This is the expected case.')
+
+ class DummySessionSocketExists():
+ def __init__(self, socket_file):
+ # simulate that connect passes
+ return
+
+ isc.cc.Session = DummySession
+
+ # All is well case, where all components are started
+ # successfully. We check that the actual call to
+ # start_all_components() is made, and BoB.runnable is true.
+ bob = MockBobStartup(False)
+ r = bob.startup()
+ self.assertIsNone(r)
+ self.assertTrue(bob.started)
+ self.assertFalse(bob.killed)
+ self.assertTrue(bob.runnable)
+ self.assertEqual({}, bob.c_channel_env)
+
+ # Case where starting components fails. We check that
+ # kill_started_components() is called right after, and
+ # BoB.runnable is not modified.
+ bob = MockBobStartup(True)
+ r = bob.startup()
+ # r contains an error message
+ self.assertEqual(r, 'Unable to start myproc: Assume starting components has failed.')
+ self.assertTrue(bob.started)
+ self.assertTrue(bob.killed)
+ self.assertFalse(bob.runnable)
+ self.assertEqual({}, bob.c_channel_env)
+
+ # Check if msgq_socket_file is carried over
+ bob = MockBobStartup(False)
+ bob.msgq_socket_file = 'foo'
+ r = bob.startup()
+ self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+
+ # Check failure of changing user results in a different message
+ bob = MockBobStartup(bind10_src.ChangeUserError('failed to chusr'))
+ r = bob.startup()
+ self.assertIn('failed to chusr', r)
+ self.assertTrue(bob.killed)
+
+ # Check the case when socket file already exists
+ isc.cc.Session = DummySessionSocketExists
+ bob = MockBobStartup(False)
+ r = bob.startup()
+ self.assertIn('already running', r)
+
+ # isc.cc.Session is restored during tearDown().
class SocketSrvTest(unittest.TestCase):
"""
@@ -1535,11 +2342,15 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.exists(self.lockfile_testpath))
os.mkdir(self.lockfile_testpath)
self.assertTrue(os.path.isdir(self.lockfile_testpath))
+ self.__isfile_orig = bind10_src.os.path.isfile
+ self.__unlink_orig = bind10_src.os.unlink
def tearDown(self):
os.rmdir(self.lockfile_testpath)
self.assertFalse(os.path.isdir(self.lockfile_testpath))
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+ bind10_src.os.path.isfile = self.__isfile_orig
+ bind10_src.os.unlink = self.__unlink_orig
def test_remove_lock_files(self):
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
@@ -1563,6 +2374,47 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
bind10_src.remove_lock_files()
+ def test_remove_lock_files_fail(self):
+ # Permission error on unlink is ignored; other exceptions are really
+ # unexpected and propagated.
+ def __raising_unlink(unused, ex):
+ raise ex
+
+ bind10_src.os.path.isfile = lambda _: True
+ os_error = OSError()
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+ os_error.errno = errno.EPERM
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.EACCES
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.ENOENT
+ self.assertRaises(OSError, bind10_src.remove_lock_files)
+
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+ self.assertRaises(Exception, bind10_src.remove_lock_files)
+
+ def test_get_signame(self):
+ # just test with some samples
+ signame = bind10_src.get_signame(signal.SIGTERM)
+ self.assertEqual('SIGTERM', signame)
+ signame = bind10_src.get_signame(signal.SIGKILL)
+ self.assertEqual('SIGKILL', signame)
+ # 59426 is hopefully an unused signal on most platforms
+ signame = bind10_src.get_signame(59426)
+ self.assertEqual('Unknown signal 59426', signame)
+
+ def test_fatal_signal(self):
+ self.assertIsNone(bind10_src.boss_of_bind)
+ bind10_src.boss_of_bind = BoB()
+ bind10_src.boss_of_bind.runnable = True
+ bind10_src.fatal_signal(signal.SIGTERM, None)
+ # Now, runnable must be False
+ self.assertFalse(bind10_src.boss_of_bind.runnable)
+ bind10_src.boss_of_bind = None
+
if __name__ == '__main__':
# store os.environ for test_unchanged_environment
original_os_environ = copy.deepcopy(os.environ)
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 37b74e7..7c2b2af 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -208,12 +208,13 @@ WARNING: Python readline module isn't available, so the command line editor
return True
def login_to_cmdctl(self):
- '''Login to cmdctl with the username and password inputted
- from user. After the login is sucessful, the username and
+ '''Login to cmdctl with the username and password given by
+ the user. After the login is sucessful, the username and
password will be saved in 'default_user.csv', when run the next
time, username and password saved in 'default_user.csv' will be
used first.
'''
+ # Look at existing username/password combinations and try to log in
users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
@@ -230,15 +231,18 @@ WARNING: Python readline module isn't available, so the command line editor
print(data + ' login as ' + row[0])
return True
+ # No valid logins were found, prompt the user for a username/password
count = 0
- print("[TEMP MESSAGE]: username :root password :bind10")
+ print('No stored password file found, please see sections '
+ '"Configuration specification for b10-cmdctl" and "bindctl '
+ 'command-line options" of the BIND 10 guide.')
while True:
count = count + 1
if count > 3:
print("Too many authentication failures")
return False
- username = input("Username:")
+ username = input("Username: ")
passwd = getpass.getpass()
param = {'username': username, 'password' : passwd}
try:
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index b9af5c2..5c6aeb2 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -364,16 +364,24 @@ class TestConfigCommands(unittest.TestCase):
socket_err_output = io.StringIO()
sys.stdout = socket_err_output
self.assertEqual(1, self.tool.run())
- self.assertEqual("Failed to send request, the connection is closed\n",
- socket_err_output.getvalue())
+
+ # First few lines may be some kind of heading, or a warning that
+ # Python readline is unavailable, so we do a sub-string check.
+ self.assertIn("Failed to send request, the connection is closed",
+ socket_err_output.getvalue())
+
socket_err_output.close()
# validate log message for http.client.CannotSendRequest
cannot_send_output = io.StringIO()
sys.stdout = cannot_send_output
self.assertEqual(1, self.tool.run())
- self.assertEqual("Can not send request, the connection is busy\n",
- cannot_send_output.getvalue())
+
+ # First few lines may be some kind of heading, or a warning that
+ # Python readline is unavailable, so we do a sub-string check.
+ self.assertIn("Can not send request, the connection is busy",
+ cannot_send_output.getvalue())
+
cannot_send_output.close()
def test_apply_cfg_command_int(self):
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index a3763c9..315e3c5 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -27,7 +27,7 @@ import glob
import os.path
import imp
import isc.log
-isc.log.init("b10-cfgmgr")
+isc.log.init("b10-cfgmgr", buffer=True)
from isc.config.cfgmgr import ConfigManager, ConfigManagerDataReadError, logger
from isc.log_messages.cfgmgr_messages import *
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index ff5706c..d8688f9 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -136,7 +136,7 @@
<refsect1>
<title>FILES</title>
<!-- TODO: fix path -->
- <para><filename>/usr/local/var/bind10-devel/b10-config.db</filename>
+ <para><filename>/usr/local/var/bind10/b10-config.db</filename>
— Configuration storage file.
</para>
</refsect1>
diff --git a/src/bin/cfgmgr/local_plugins/.gitignore b/src/bin/cfgmgr/local_plugins/.gitignore
new file mode 100644
index 0000000..724690c
--- /dev/null
+++ b/src/bin/cfgmgr/local_plugins/.gitignore
@@ -0,0 +1 @@
+/datasrc.spec
diff --git a/src/bin/cmdctl/.gitignore b/src/bin/cmdctl/.gitignore
index 01e3ef0..e702e49 100644
--- a/src/bin/cmdctl/.gitignore
+++ b/src/bin/cmdctl/.gitignore
@@ -1,6 +1,10 @@
+/b10-certgen
+/b10-certgen.1
/b10-cmdctl
+/b10-cmdctl.8
+/cmdctl-certfile.pem
+/cmdctl-keyfile.pem
/cmdctl.py
/cmdctl.spec
/cmdctl.spec.pre
/run_b10-cmdctl.sh
-/b10-cmdctl.8
diff --git a/src/bin/cmdctl/Makefile.am b/src/bin/cmdctl/Makefile.am
index 3b88f4b..bfc13af 100644
--- a/src/bin/cmdctl/Makefile.am
+++ b/src/bin/cmdctl/Makefile.am
@@ -4,6 +4,8 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-cmdctl
+bin_PROGRAMS = b10-certgen
+
nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
pylogmessagedir = $(pyexecdir)/isc/log_messages/
@@ -25,15 +27,18 @@ CLEANFILES= b10-cmdctl cmdctl.pyc cmdctl.spec
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.pyc
-man_MANS = b10-cmdctl.8
-DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST += $(man_MANS) b10-cmdctl.xml cmdctl_messages.mes
+man_MANS = b10-cmdctl.8 b10-certgen.1
+DISTCLEANFILES = $(man_MANS) cmdctl-certfile.pem cmdctl-keyfile.pem
+EXTRA_DIST += $(man_MANS) b10-certgen.xml b10-cmdctl.xml cmdctl_messages.mes
if GENERATE_DOCS
b10-cmdctl.8: b10-cmdctl.xml
@XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-cmdctl.xml
+b10-certgen.1: b10-certgen.xml
+ @XSLTPROC@ --novalid --xinclude --nonet -o $@ http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $(srcdir)/b10-certgen.xml
+
else
$(man_MANS):
@@ -54,12 +59,23 @@ b10-cmdctl: cmdctl.py $(PYTHON_LOGMSGPKG_DIR)/work/cmdctl_messages.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" cmdctl.py >$@
chmod a+x $@
+b10_certgen_SOURCES = b10-certgen.cc
+b10_certgen_CXXFLAGS = $(BOTAN_INCLUDES)
+b10_certgen_LDFLAGS = $(BOTAN_LIBS)
+
+# Generate the initial certificates immediately
+cmdctl-certfile.pem: b10-certgen
+ ./b10-certgen -q -w
+
+cmdctl-keyfile.pem: b10-certgen
+ ./b10-certgen -q -w
+
if INSTALL_CONFIGURATIONS
# Below we intentionally use ${INSTALL} -m 640 instead of $(INSTALL_DATA)
# because these file will contain sensitive information.
install-data-local:
- $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
+ $(mkinstalldirs) $(DESTDIR)/@sysconfdir@/@PACKAGE@
for f in $(CMDCTL_CONFIGURATIONS) ; do \
if test ! -f $(DESTDIR)$(sysconfdir)/@PACKAGE@/$$f; then \
${INSTALL} -m 640 $(srcdir)/$$f $(DESTDIR)$(sysconfdir)/@PACKAGE@/ ; \
diff --git a/src/bin/cmdctl/b10-certgen.cc b/src/bin/cmdctl/b10-certgen.cc
new file mode 100644
index 0000000..579ae60
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.cc
@@ -0,0 +1,429 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <botan/botan.h>
+#include <botan/x509self.h>
+#include <botan/x509stor.h>
+#include <botan/rsa.h>
+#include <botan/dsa.h>
+#include <botan/data_src.h>
+using namespace Botan;
+
+#include <iostream>
+#include <fstream>
+#include <memory>
+#include <getopt.h>
+
+// For cleaner 'does not exist or is not readable' output than
+// botan provides
+#include <unistd.h>
+#include <errno.h>
+
+// This is a simple tool that creates a self-signed PEM certificate
+// for use with BIND 10. It creates a simple certificate for initial
+// setup. Currently, all values are hardcoded defaults. For future
+// versions, we may want to add more options for administrators.
+
+// It will create a PEM file containing a certificate with the following
+// values:
+// common name: localhost
+// organization: BIND10
+// country code: US
+
+// Additional error return codes; these are specifically
+// chosen to be distinct from validation error codes as
+// provided by Botan. Their main use is to distinguish
+// error cases in the unit tests.
+const int DECODING_ERROR = 100;
+const int BAD_OPTIONS = 101;
+const int READ_ERROR = 102;
+const int WRITE_ERROR = 103;
+const int UNKNOWN_ERROR = 104;
+const int NO_SUCH_FILE = 105;
+const int FILE_PERMISSION_ERROR = 106;
+
+void
+usage() {
+ std::cout << "Usage: b10-certgen [OPTION]..." << std::endl;
+ std::cout << "Validate, create, or update a self-signed certificate for "
+ "use with b10-cmdctl" << std::endl;
+ std::cout << "" << std::endl;
+ std::cout << "Options:" << std::endl;
+ std::cout << "-c, --certfile=FILE\t\tfile to read or store the certificate"
+ << std::endl;
+ std::cout << "-f, --force\t\t\toverwrite existing certficate even if it"
+ << std::endl <<"\t\t\t\tis valid" << std::endl;
+ std::cout << "-h, --help\t\t\tshow this help" << std::endl;
+ std::cout << "-k, --keyfile=FILE\t\tfile to store the generated private key"
+ << std::endl;
+ std::cout << "-w, --write\t\t\tcreate a new certificate if the given file"
+ << std::endl << "\t\t\t\tdoes not exist, or if is is not valid"
+ << std::endl;
+ std::cout << "-q, --quiet\t\t\tprint no output when creating or validating"
+ << std::endl;
+}
+
+/// \brief Returns true if the given file exists
+///
+/// \param filename The file to check
+/// \return true if file exists
+bool
+fileExists(const std::string& filename) {
+ return (access(filename.c_str(), F_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is readable
+///
+/// \param filename The file to check
+/// \return true if file exists and is readable
+bool
+fileIsReadable(const std::string& filename) {
+ return (access(filename.c_str(), R_OK) == 0);
+}
+
+/// \brief Returns true if the given file exists and is writable
+///
+/// \param filename The file to check
+/// \return true if file exists and is writable
+bool
+fileIsWritable(const std::string& filename) {
+ return (access(filename.c_str(), W_OK) == 0);
+}
+
+/// \brief Helper function for readable error output;
+///
+/// Returns string representation of X509 result code
+/// This does not appear to be provided by Botan itself
+///
+/// \param code An \c X509_Code instance
+/// \return A human-readable c string
+const char*
+X509CodeToString(const X509_Code& code) {
+ // note that this list provides more than we would
+ // need in this context, it is just the enum from
+ // the source code of Botan.
+ switch (code) {
+ case VERIFIED:
+ return ("verified");
+ case UNKNOWN_X509_ERROR:
+ return ("unknown x509 error");
+ case CANNOT_ESTABLISH_TRUST:
+ return ("cannot establish trust");
+ case CERT_CHAIN_TOO_LONG:
+ return ("certificate chain too long");
+ case SIGNATURE_ERROR:
+ return ("signature error");
+ case POLICY_ERROR:
+ return ("policy error");
+ case INVALID_USAGE:
+ return ("invalid usage");
+ case CERT_FORMAT_ERROR:
+ return ("certificate format error");
+ case CERT_ISSUER_NOT_FOUND:
+ return ("certificate issuer not found");
+ case CERT_NOT_YET_VALID:
+ return ("certificate not yet valid");
+ case CERT_HAS_EXPIRED:
+ return ("certificate has expired");
+ case CERT_IS_REVOKED:
+ return ("certificate has been revoked");
+ case CRL_FORMAT_ERROR:
+ return ("crl format error");
+ case CRL_NOT_YET_VALID:
+ return ("crl not yet valid");
+ case CRL_HAS_EXPIRED:
+ return ("crl has expired");
+ case CA_CERT_CANNOT_SIGN:
+ return ("CA cert cannot sign");
+ case CA_CERT_NOT_FOR_CERT_ISSUER:
+ return ("CA certificate not for certificate issuer");
+ case CA_CERT_NOT_FOR_CRL_ISSUER:
+ return ("CA certificate not for crl issuer");
+ default:
+ return ("Unknown X509 code");
+ }
+}
+
+class CertificateTool {
+public:
+ CertificateTool(bool quiet) : quiet_(quiet) {}
+
+ int
+ createKeyAndCertificate(const std::string& key_file_name,
+ const std::string& cert_file_name) {
+ try {
+ AutoSeeded_RNG rng;
+
+ // Create and store a private key
+ print("Creating key file " + key_file_name);
+ RSA_PrivateKey key(rng, 2048);
+ std::ofstream key_file(key_file_name.c_str());
+ if (!key_file.good()) {
+ print(std::string("Error writing to ") + key_file_name +
+ ": " + std::strerror(errno));
+ return (WRITE_ERROR);
+ }
+ key_file << PKCS8::PEM_encode(key, rng, "");
+ if (!key_file.good()) {
+ print(std::string("Error writing to ") + key_file_name +
+ ": " + std::strerror(errno));
+ return (WRITE_ERROR);
+ }
+ key_file.close();
+
+ // Certificate options, currently hardcoded.
+ // For a future version we may want to make these
+ // settable.
+ X509_Cert_Options opts;
+ opts.common_name = "localhost";
+ opts.organization = "UNKNOWN";
+ opts.country = "XX";
+
+ opts.CA_key();
+
+ print("Creating certificate file " + cert_file_name);
+
+ // The exact call changed aftert 1.8, adding the
+ // hash function option
+#if BOTAN_VERSION_CODE >= BOTAN_VERSION_CODE_FOR(1,9,0)
+ X509_Certificate cert =
+ X509::create_self_signed_cert(opts, key, "SHA-256", rng);
+#else
+ X509_Certificate cert =
+ X509::create_self_signed_cert(opts, key, rng);
+#endif
+
+ std::ofstream cert_file(cert_file_name.c_str());
+ if (!cert_file.good()) {
+ print(std::string("Error writing to ") + cert_file_name +
+ ": " + std::strerror(errno));
+ return (WRITE_ERROR);
+ }
+ cert_file << cert.PEM_encode();
+ if (!cert_file.good()) {
+ print(std::string("Error writing to ") + cert_file_name +
+ ": " + std::strerror(errno));
+ return (WRITE_ERROR);
+ }
+ cert_file.close();
+ } catch(std::exception& e) {
+ std::cout << "Error creating key or certificate: " << e.what()
+ << std::endl;
+ return (UNKNOWN_ERROR);
+ }
+ return (0);
+ }
+
+ int
+ validateCertificate(const std::string& certfile) {
+ // Since we are dealing with a self-signed certificate here, we
+ // also use the certificate to check itself; i.e. we add it
+ // as a trusted certificate, then validate the certficate itself.
+ //const X509_Certificate cert(certfile);
+ try {
+ X509_Store store;
+ DataSource_Stream in(certfile);
+ store.add_trusted_certs(in);
+
+ const X509_Code result = store.validate_cert(certfile);
+
+ if (result == VERIFIED) {
+ print(certfile + " is valid");
+ } else {
+ print(certfile + " failed to verify: " +
+ X509CodeToString(result));
+ }
+ return (result);
+ } catch (const Botan::Decoding_Error& bde) {
+ print(certfile + " failed to verify: " + bde.what());
+ return (DECODING_ERROR);
+ } catch (const Botan::Stream_IO_Error& bsie) {
+ print(certfile + " not read: " + bsie.what());
+ return (READ_ERROR);
+ }
+ }
+
+ /// \brief Runs the tool
+ ///
+ /// \param create_cert Create certificate if true, validate if false.
+ /// Does nothing if certificate exists and is valid.
+ /// \param force_create Create new certificate even if it is valid.
+ /// \param certfile Certificate file to read to or write from.
+ /// \param keyfile Key file to write if certificate is created.
+ /// Ignored if create_cert is false
+ /// \return zero on success, non-zero on failure
+ int
+ run(bool create_cert, bool force_create, const std::string& certfile,
+ const std::string& keyfile)
+ {
+ if (create_cert) {
+ // Unless force is given, only create it if the current
+ // one is not OK
+
+ // First do some basic permission checks; both files
+ // should either not exist, or be both readable
+ // and writable
+ // The checks are done one by one so all errors can
+ // be enumerated in one go
+ if (fileExists(certfile)) {
+ if (!fileIsReadable(certfile)) {
+ print(certfile + " not readable: " + std::strerror(errno));
+ create_cert = false;
+ }
+ if (!fileIsWritable(certfile)) {
+ print(certfile + " not writable: " + std::strerror(errno));
+ create_cert = false;
+ }
+ }
+ // The key file really only needs write permissions (for
+ // b10-certgen that is)
+ if (fileExists(keyfile)) {
+ if (!fileIsWritable(keyfile)) {
+ print(keyfile + " not writable: " + std::strerror(errno));
+ create_cert = false;
+ }
+ }
+ if (!create_cert) {
+ print("Not creating new certificate, "
+ "check file permissions");
+ return (FILE_PERMISSION_ERROR);
+ }
+
+ // If we reach this, we know that if they exist, we can both
+ // read and write them, so now it's up to content checking
+ // and/or force_create
+
+ if (force_create || !fileExists(certfile) ||
+ validateCertificate(certfile) != VERIFIED) {
+ return (createKeyAndCertificate(keyfile, certfile));
+ } else {
+ print("Not creating a new certificate (use -f to force)");
+ }
+ } else {
+ if (!fileExists(certfile)) {
+ print(certfile + ": " + std::strerror(errno));
+ return (NO_SUCH_FILE);
+ }
+ if (!fileIsReadable(certfile)) {
+ print(certfile + " not readable: " + std::strerror(errno));
+ return (FILE_PERMISSION_ERROR);
+ }
+ int result = validateCertificate(certfile);
+ if (result != 0) {
+ print("Running with -w would overwrite the certificate");
+ }
+ return (result);
+ }
+ return (0);
+ }
+private:
+ /// Prints the message to stdout unless quiet_ is true
+ void print(const std::string& msg) {
+ if (!quiet_) {
+ std::cout << msg << std::endl;
+ }
+ }
+
+ bool quiet_;
+};
+
+int
+main(int argc, char* argv[])
+{
+ Botan::LibraryInitializer init;
+
+ // create or check certificate
+ bool create_cert = false;
+ // force creation even if not necessary
+ bool force_create = false;
+ // don't print any output
+ bool quiet = false;
+
+ // default certificate file
+ std::string certfile("cmdctl-certfile.pem");
+ // default key file
+ std::string keyfile("cmdctl-keyfile.pem");
+
+ // whether or not the above values have been
+ // overridden (used in command line checking)
+ bool certfile_default = true;
+ bool keyfile_default = true;
+
+ // It would appear some environments insist on
+ // char* here (Sunstudio on Solaris), so we const_cast
+ // them to get rid of compiler warnings.
+ const struct option long_options[] = {
+ { const_cast<char*>("certfile"), required_argument, NULL, 'c' },
+ { const_cast<char*>("force"), no_argument, NULL, 'f' },
+ { const_cast<char*>("help"), no_argument, NULL, 'h' },
+ { const_cast<char*>("keyfile"), required_argument, NULL, 'k' },
+ { const_cast<char*>("write"), no_argument, NULL, 'w' },
+ { const_cast<char*>("quiet"), no_argument, NULL, 'q' },
+ { NULL, 0, NULL, 0 }
+ };
+
+ int opt, option_index;
+ while ((opt = getopt_long(argc, argv, "c:fhk:wq", long_options,
+ &option_index)) != -1) {
+ switch (opt) {
+ case 'c':
+ certfile = optarg;
+ certfile_default = false;
+ break;
+ case 'f':
+ force_create = true;
+ break;
+ case 'h':
+ usage();
+ return (0);
+ break;
+ case 'k':
+ keyfile = optarg;
+ keyfile_default = false;
+ break;
+ case 'w':
+ create_cert = true;
+ break;
+ case 'q':
+ quiet = true;
+ break;
+ default:
+ // A message will have already been output about the error.
+ return (BAD_OPTIONS);
+ }
+ }
+
+ if (optind < argc) {
+ std::cout << "Error: extraneous arguments" << std::endl << std::endl;
+ usage();
+ return (BAD_OPTIONS);
+ }
+
+ // Some sanity checks on option combinations
+ if (create_cert && (certfile_default ^ keyfile_default)) {
+ std::cout << "Error: keyfile and certfile must both be specified "
+ "if one of them is when calling b10-certgen in write "
+ "mode." << std::endl;
+ return (BAD_OPTIONS);
+ }
+ if (!create_cert && !keyfile_default) {
+ std::cout << "Error: keyfile is not used when not in write mode"
+ << std::endl;
+ return (BAD_OPTIONS);
+ }
+
+ // Initialize the tool and perform the appropriate action(s)
+ CertificateTool tool(quiet);
+ return (tool.run(create_cert, force_create, certfile, keyfile));
+}
diff --git a/src/bin/cmdctl/b10-certgen.xml b/src/bin/cmdctl/b10-certgen.xml
new file mode 100644
index 0000000..5ac8783
--- /dev/null
+++ b/src/bin/cmdctl/b10-certgen.xml
@@ -0,0 +1,214 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
+ [<!ENTITY mdash "—">]>
+<!--
+ - Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+ -
+ - Permission to use, copy, modify, and/or distribute this software for any
+ - purpose with or without fee is hereby granted, provided that the above
+ - copyright notice and this permission notice appear in all copies.
+ -
+ - THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ - REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ - AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ - INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ - LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ - OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ - PERFORMANCE OF THIS SOFTWARE.
+-->
+
+<refentry>
+
+ <refentryinfo>
+ <date>November 15, 2012</date>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>b10-certgen</refentrytitle>
+ <manvolnum>1</manvolnum>
+ <refmiscinfo>BIND10</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>b10-certgen</refname>
+ <refpurpose>X509 Certificate generation tool for use with b10-cmdctl</refpurpose>
+ </refnamediv>
+
+ <docinfo>
+ <copyright>
+ <year>2012</year>
+ <holder>Internet Systems Consortium, Inc. ("ISC")</holder>
+ </copyright>
+ </docinfo>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>b10-certgen</command>
+ <group choice="opt">
+ <arg choice="[OPTION]..."><option>-</option></arg>
+ </group>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>DESCRIPTION</title>
+ <para>The <command>b10-certgen</command> tool validates, creates, or
+ updates a self-signed X509 certificate for use in b10-cmdctl.
+ </para>
+
+ <para>
+ The connection between <command>bindctl</command> and
+ <command>b10-cmdctl</command> is done over HTTPS, and therefore
+ <command>b10-cmdctl</command> needs a certificate. Since these
+ certificates have expiry dates, they also need to be regenerated at
+ some point.
+
+ There are many tools to do so, but for ease of use, <command>
+ b10-certgen</command> can create a simple self-signed certificate.
+
+ By default, it will not create anything, but it will merely check an
+ existing certificate (if not specified, cmdctl-certfile.pem, in the
+ current working directory). And print whether it is valid, and
+ whether it would update if the option '-w' is given.
+
+ With that option, the certificate could then be replaced by a newly
+ created one. If the certificate is still valid, it would still not
+ be overwritten (however, if it is found to be invalid, for example
+ because it has expired, it would create a new one).
+
+ A new certificate is always created if the certificate file does
+ not exist, or if creation is forced (with the -f option).
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>ARGUMENTS</title>
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+
+ <varlistentry>
+ <term>
+ <option>-c <replaceable>file</replaceable></option>,
+ <option>--certfile=<replaceable>file</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ File to read the certificate from, or write the certificate to.
+ If <option>-w</option> and <option>-c</option> are used,
+ <option>-k</option> is mandatory as well.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-f</option>,
+ <option>--force</option>
+ </term>
+ <listitem>
+ <para>
+ Force updating of certificate when <option>-w</option> is used,
+ even if the existing certificate is still valid.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-h</option>,
+ <option>--help</option>
+ </term>
+ <listitem>
+ <para>
+ Print the command line arguments and exit.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-k <replaceable>file</replaceable></option>,
+ <option>--keyfile=<replaceable>file</replaceable></option>
+ </term>
+ <listitem>
+ <para>
+ File to write the private key to. This option is only valid when <option>-w</option> is used, and if this option is used, <option>-c</option> is mandatory as well.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-w</option>,
+ <option>--write</option>
+ </term>
+ <listitem>
+ <para>
+ Check the given certificate file. If it does not exist, a new
+ private key and certificate are created. If it does exist, the
+ certificate is validated. If it is not valid (for instance
+ because it has expired), it is overwritten with a newly created
+ certificate. If it is valid, nothing happens (use
+ <option>-f</option> to force an update in that case).
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>
+ <option>-q</option>,
+ <option>--quiet</option>
+ </term>
+ <listitem>
+ <para>
+ Don't print informational messages (only command-line errors are
+ printed). Useful in scripts when only the return code is needed.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>SEE ALSO</title>
+ <para>
+ <citerefentry>
+ <refentrytitle>b10-cmdctl</refentrytitle><manvolnum>8</manvolnum>
+ </citerefentry>,
+ <citetitle>BIND 10 Guide</citetitle>.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>HISTORY</title>
+ <para>
+ The <command>b10-certgen</command> tool was first implemented
+ in November 2012 for the ISC BIND 10 project.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>EXAMPLE</title>
+ <para>
+ To update an expired certificate in BIND 10 that has been installed to
+ /usr/local:
+ <screen>
+$> cd /usr/local/etc/bind10/
+
+$> b10-certgen
+cmdctl-certfile.pem failed to verify: certificate has expired
+Running with -w would overwrite the certificate
+
+$> b10-certgen --write
+cmdctl-certfile.pem failed to verify: certificate has expired
+Creating key file cmdctl-keyfile.pem
+Creating certificate file cmdctl-certfile.pem
+
+$> b10-certgen --write
+cmdctl-certfile.pem is valid
+Not creating a new certificate (use -f to force)
+ </screen>
+ </para>
+ </refsect1>
+</refentry><!--
+ - Local variables:
+ - mode: sgml
+ - End:
+-->
diff --git a/src/bin/cmdctl/b10-cmdctl.xml b/src/bin/cmdctl/b10-cmdctl.xml
index e01d5a2..4b1b32f 100644
--- a/src/bin/cmdctl/b10-cmdctl.xml
+++ b/src/bin/cmdctl/b10-cmdctl.xml
@@ -147,21 +147,21 @@
<varname>accounts_file</varname> defines the path to the
user accounts database.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>.
</para>
<para>
<varname>cert_file</varname> defines the path to the
PEM certificate file.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>.
</para>
<para>
<varname>key_file</varname> defines the path to the PEM private key
file.
The default is
- <filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>.
+ <filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>.
</para>
<!-- TODO: formating -->
@@ -187,17 +187,17 @@
<!-- TODO: permissions -->
<!-- TODO: what about multiple accounts? -->
<!-- TODO: shouldn't the password file name say cmdctl in it? -->
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-accounts.csv</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-accounts.csv</filename>
— account database containing the name, hashed password,
and the salt.
</para>
<!-- TODO: replace /usr/local -->
<!-- TODO: permissions -->
<!-- TODO: shouldn't have both in same file, will be configurable -->
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-keyfile.pem</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-keyfile.pem</filename>
— contains the Private key.
</para>
- <para><filename>/usr/local/etc/bind10-devel/cmdctl-certfile.pem</filename>
+ <para><filename>/usr/local/etc/bind10/cmdctl-certfile.pem</filename>
— contains the Certificate.
</para>
</refsect1>
diff --git a/src/bin/cmdctl/cmdctl-certfile.pem b/src/bin/cmdctl/cmdctl-certfile.pem
deleted file mode 100644
index 384a222..0000000
--- a/src/bin/cmdctl/cmdctl-certfile.pem
+++ /dev/null
@@ -1,21 +0,0 @@
------BEGIN CERTIFICATE-----
-MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
-VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
-A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
-MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
-NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
-ZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
-CxMFY25uaWMxEzARBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
-YW5nbGlrdW5AY25uaWMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
-JbEkYoy9SEsU9t/mfxlaiCqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
-UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
-O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
-BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
-tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
-amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
-BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
-Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
-9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
-jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
-EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
------END CERTIFICATE-----
diff --git a/src/bin/cmdctl/cmdctl-keyfile.pem b/src/bin/cmdctl/cmdctl-keyfile.pem
deleted file mode 100644
index 8fff2dc..0000000
--- a/src/bin/cmdctl/cmdctl-keyfile.pem
+++ /dev/null
@@ -1,15 +0,0 @@
------BEGIN RSA PRIVATE KEY-----
-MIICXAIBAAKBgQDpICWxJGKMvUhLFPbf5n8ZWogqjYcQqqoHqHVRHYjyiey6FZdt
-ZkY2s1gYh0G0NXtimlIgic+vEcFe7vdmyKntW7DYDaqAj0KrED7RKAj8324jNbSJ
-HtLP4evvJep3vsoNtTvNuceQJ46vukxyxgg3DuC9kVqPuD8CZ1Rq4ATyiwIDAQAB
-AoGBAOJlOtV+DUq6Y2Ou91VXRiU8GzKgAQP5iWgoe84Ljbxkn4XThBxVD2j94Fbp
-u7AjpDCMx6cbzpoo9w6XqaGizAmAehIfTE3eFYs74N/FM09Wg2OSDyxMY0jgyECU
-A4ukjlPwcGDbmgbmlY3i+FVHp+zCgtZEsMC1IAosMac1BoX5AkEA/lrXWaVtH8bo
-mut3GBaXvubZMdaUr0BUd5a9q+tt4dQcKG1kFqgCNKhNhBIcpiMVcz+jGmOuopNA
-8dnUGqv3FQJBAOqiJ54ZvOTWNDpJIe02wIXRxRmc1xhHFCqYP23KxBVrAcTYB19J
-lesov/hEbnGLCbKS/naZJ1zrTImUPNRLqx8CQCzDtA7U7GWhTiKluioFH+O7IRKC
-X1yQh80cPHlbT9VkzSfYSLssCmdWD35k6aHbntTPqFbmoD+AhveJjKi9BxkCQDwX
-1c+/RcrSNcQr0N2hZUOgyztZGRnlsnuKTMyA3yGhK23P6mt0PEpjQG+Ej0jTVGOB
-FF0pspQwy4R9C+tPif8CQH36NNlXBfVNmT7kDtyLmaE6pID0vY9duX56BJbU1R0x
-SQ8/LcfJagk6gvp08OyYCPA+WZ7u/bas9R/nMTCLivc=
------END RSA PRIVATE KEY-----
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index 4076f4c..52af54a 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -49,7 +49,7 @@ from hashlib import sha1
from isc.util import socketserver_mixin
from isc.log_messages.cmdctl_messages import *
-isc.log.init("b10-cmdctl")
+isc.log.init("b10-cmdctl", buffer=True)
logger = isc.log.Logger("cmdctl")
# Debug level for communication with BIND10
diff --git a/src/bin/cmdctl/tests/Makefile.am b/src/bin/cmdctl/tests/Makefile.am
index b5b65f6..6d8f282 100644
--- a/src/bin/cmdctl/tests/Makefile.am
+++ b/src/bin/cmdctl/tests/Makefile.am
@@ -1,6 +1,9 @@
PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
-PYTESTS = cmdctl_test.py
+PYTESTS = cmdctl_test.py b10-certgen_test.py
EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/expired-certfile.pem
+EXTRA_DIST += testdata/mangled-certfile.pem
+EXTRA_DIST += testdata/noca-certfile.pem
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -9,10 +12,12 @@ if SET_ENV_LIBRARY_PATH
LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
endif
+CLEANFILES = test-keyfile.pem test-certfile.pem
+
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
if ENABLE_PYTHON_COVERAGE
- touch $(abs_top_srcdir)/.coverage
+ touch $(abs_top_srcdir)/.coverage
rm -f .coverage
${LN_S} $(abs_top_srcdir)/.coverage .coverage
endif
diff --git a/src/bin/cmdctl/tests/b10-certgen_test.py b/src/bin/cmdctl/tests/b10-certgen_test.py
new file mode 100644
index 0000000..d54efa3
--- /dev/null
+++ b/src/bin/cmdctl/tests/b10-certgen_test.py
@@ -0,0 +1,254 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+# Note: the main code is in C++, but what we are mostly testing is
+# options and behaviour (output/file creation, etc), which is easier
+# to test in python.
+
+import unittest
+import os
+from subprocess import call
+import subprocess
+import ssl
+import stat
+
+def run(command):
+ """
+ Small helper function that returns a tuple of (rcode, stdout, stderr) after
+ running the given command (an array of command and arguments, as passed on
+ to subprocess).
+ """
+ subp = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ (stdout, stderr) = subp.communicate()
+ return (subp.returncode, stdout, stderr)
+
+class FileDeleterContext:
+ """
+ Simple Context Manager that deletes a given set of files when the context
+ is left.
+ """
+ def __init__(self, files):
+ self.files = files
+
+ def __enter__(self):
+ pass
+
+ def __exit__(self, type, value, traceback):
+ for f in self.files:
+ if os.path.exists(f):
+ os.unlink(f)
+
+class FilePermissionContext:
+ """
+ Simple Context Manager that temporarily modifies file permissions for
+ a given file
+ """
+ def __init__(self, f, unset_flags = [], set_flags = []):
+ """
+ Initialize file permission context.
+ See the stat module for possible flags to set or unset.
+ The flags are changed when the context is entered (i.e.
+ you can create the context first without any change)
+ The flags are changed back when the context is left.
+
+ Parameters:
+ f: string, file to change permissions for
+ unset_flags: list of flags to unset
+ set_flags: list of flags to set
+ """
+ self.file = f
+ self.orig_mode = os.stat(f).st_mode
+ new_mode = self.orig_mode
+ for flag in unset_flags:
+ new_mode = new_mode & ~flag
+ for flag in set_flags:
+ new_mode = new_mode | flag
+ self.new_mode = new_mode
+
+ def __enter__(self):
+ os.chmod(self.file, self.new_mode)
+
+ def __exit__(self, type, value, traceback):
+ os.chmod(self.file, self.orig_mode)
+
+def read_file_data(filename):
+ """
+ Simple text file reader that returns its contents as an array
+ """
+ with open(filename) as f:
+ return f.readlines()
+
+class TestCertGenTool(unittest.TestCase):
+ TOOL = '../b10-certgen'
+
+ def run_check(self, expected_returncode, expected_stdout, expected_stderr, command):
+ """
+ Runs the given command, and checks return code, and outputs (if provided).
+ Arguments:
+ expected_returncode, return code of the command
+ expected_stdout, (multiline) string that is checked agains stdout.
+ May be None, in which case the check is skipped.
+ expected_stderr, (multiline) string that is checked agains stderr.
+ May be None, in which case the check is skipped.
+ """
+ (returncode, stdout, stderr) = run(command)
+ self.assertEqual(expected_returncode, returncode, " ".join(command))
+ if expected_stdout is not None:
+ self.assertEqual(expected_stdout, stdout.decode())
+ if expected_stderr is not None:
+ self.assertEqual(expected_stderr, stderr.decode())
+
+ def validate_certificate(self, expected_result, certfile):
+ """
+ Validate a certificate, using the quiet option of the tool; it runs
+ the check option (-c) for the given base name of the certificate (-f
+ <certfile>), and compares the return code to the given
+ expected_result value
+ """
+ self.run_check(expected_result, '', '',
+ [self.TOOL, '-q', '-c', certfile])
+ # Same with long options
+ self.run_check(expected_result, '', '',
+ [self.TOOL, '--quiet', '--certfile', certfile])
+
+
+ def test_basic_creation(self):
+ """
+ Tests whether basic creation with no arguments (except output
+ file name) successfully creates a key and certificate
+ """
+ keyfile = 'test-keyfile.pem'
+ certfile = 'test-certfile.pem'
+ command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+ self.creation_helper(command, certfile, keyfile)
+ # Do same with long options
+ command = [ self.TOOL, '--quiet', '--write', '--certfile=' + certfile, '--keyfile=' + keyfile ]
+ self.creation_helper(command, certfile, keyfile)
+
+ def creation_helper(self, command, certfile, keyfile):
+ """
+ Helper method for test_basic_creation.
+ Performs the actual checks
+ """
+ with FileDeleterContext([keyfile, certfile]):
+ self.assertFalse(os.path.exists(keyfile))
+ self.assertFalse(os.path.exists(certfile))
+ self.run_check(0, '', '', command)
+ self.assertTrue(os.path.exists(keyfile))
+ self.assertTrue(os.path.exists(certfile))
+
+ # Validate the certificate that was just created
+ self.validate_certificate(0, certfile)
+
+ # When run with the same options, it should *not* create it again,
+ # as the current certificate should still be valid
+ certdata = read_file_data(certfile)
+ keydata = read_file_data(keyfile)
+
+ self.run_check(0, '', '', command)
+
+ self.assertEqual(certdata, read_file_data(certfile))
+ self.assertEqual(keydata, read_file_data(keyfile))
+
+ # but if we add -f, it should force a new creation
+ command.append('-f')
+ self.run_check(0, '', '', command)
+ self.assertNotEqual(certdata, read_file_data(certfile))
+ self.assertNotEqual(keydata, read_file_data(keyfile))
+
+ def test_check_bad_certificates(self):
+ """
+ Tests a few pre-created certificates with the -c option
+ """
+ if ('CMDCTL_SRC_PATH' in os.environ):
+ path = os.environ['CMDCTL_SRC_PATH'] + "/tests/testdata/"
+ else:
+ path = "testdata/"
+ self.validate_certificate(10, path + 'expired-certfile.pem')
+ self.validate_certificate(100, path + 'mangled-certfile.pem')
+ self.validate_certificate(17, path + 'noca-certfile.pem')
+
+ def test_bad_options(self):
+ """
+ Tests some combinations of commands that should fail.
+ """
+ # specify -c but not -k
+ self.run_check(101,
+ 'Error: keyfile and certfile must both be specified '
+ 'if one of them is when calling b10-certgen in write '
+ 'mode.\n',
+ '', [self.TOOL, '-w', '-c', 'foo'])
+ self.run_check(101,
+ 'Error: keyfile and certfile must both be specified '
+ 'if one of them is when calling b10-certgen in write '
+ 'mode.\n',
+ '', [self.TOOL, '-w', '-k', 'foo'])
+ self.run_check(101,
+ 'Error: keyfile is not used when not in write mode\n',
+ '', [self.TOOL, '-k', 'foo'])
+ # Extraneous argument
+ self.run_check(101, None, None, [self.TOOL, 'foo'])
+ # No such file
+ self.run_check(105, None, None, [self.TOOL, '-c', 'foo'])
+
+ def test_permissions(self):
+ """
+ Test some combinations of correct and bad permissions.
+ """
+ keyfile = 'mod-keyfile.pem'
+ certfile = 'mod-certfile.pem'
+ command = [ self.TOOL, '-q', '-w', '-c', certfile, '-k', keyfile ]
+ # Delete them at the end
+ with FileDeleterContext([keyfile, certfile]):
+ # Create the two files first
+ self.run_check(0, '', '', command)
+ self.validate_certificate(0, certfile)
+
+ # Make the key file unwritable
+ with FilePermissionContext(keyfile, unset_flags = [stat.S_IWUSR]):
+ self.run_check(106, '', '', command)
+ # Should have no effect on validation
+ self.validate_certificate(0, certfile)
+
+ # Make the cert file unwritable
+ with FilePermissionContext(certfile, unset_flags = [stat.S_IWUSR]):
+ self.run_check(106, '', '', command)
+ # Should have no effect on validation
+ self.validate_certificate(0, certfile)
+
+ # Make the key file unreadable (this should not matter)
+ with FilePermissionContext(keyfile, unset_flags = [stat.S_IRUSR]):
+ self.run_check(0, '', '', command)
+
+ # unreadable key file should also not have any effect on
+ # validation
+ self.validate_certificate(0, certfile)
+
+ # Make the cert file unreadable (this should matter)
+ with FilePermissionContext(certfile, unset_flags = [stat.S_IRUSR]):
+ self.run_check(106, '', '', command)
+
+ # Unreadable cert file should also fail validation
+ self.validate_certificate(106, certfile)
+
+ # Not directly a permission problem, but trying to check or create
+ # in a nonexistent directory returns different error codes
+ self.validate_certificate(105, 'fakedir/cert')
+ self.run_check(103, '', '', [ self.TOOL, '-q', '-w', '-c',
+ 'fakedir/cert', '-k', 'fakedir/key' ])
+
+if __name__== '__main__':
+ unittest.main()
+
diff --git a/src/bin/cmdctl/tests/testdata/expired-certfile.pem b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
new file mode 100644
index 0000000..384a222
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/expired-certfile.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMRAwDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxEzARBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25uaWMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxlaiCqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
diff --git a/src/bin/cmdctl/tests/testdata/mangled-certfile.pem b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
new file mode 100644
index 0000000..7c47fda
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/mangled-certfile.pem
@@ -0,0 +1,21 @@
+-----BEGIN CERTIFICATE-----
+MIIDhzCCAvCgAwIBAgIJALwngNFik7ONMA0GCSqGSIb3DQEBBQUAMIGKMQswCQYD
+VQQGEwJjbjEQMA4GA1UECBMHYmVpamluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwG
+A1UEChMFY25uaWMxDjAMBgNVBAsTBWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3Vu
+MSIwIAYJKoZIhvcNAQkBFhN6aGFuZ2xpa3VuQGNubmljLmNuMB4XDTEwMDEwNzEy
+NDcxOFoXDTExMDEwNzEyNDcxOFowgYoxCzAJBgNVBAYTAmNuMRAwDgYDVQQIEwdi
+ZWlqaW5nMraWDgYDVQQHEwdiZWlqaW5nMQ4wDAYDVQQKEwVjbm5pYzEOMAwGA1UE
+CxMFY25uaWMxeZaRBgNVBAMTCnpoYW5nbGlrdW4xIjAgBgkqhkiG9w0BCQEWE3po
+YW5nbGlrdW5AY25UAwMuY24wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOkg
+JbEkYoy9SEsU9t/mfxLAICqNhxCqqgeodVEdiPKJ7LoVl21mRjazWBiHQbQ1e2Ka
+UiCJz68RwV7u92bIqe1bsNgNqoCPQqsQPtEoCPzfbiM1tIke0s/h6+8l6ne+yg21
+O825x5Anjq+6THLGCDcO4L2RWo+4PwJnVGrgBPKLAgMBAAGjgfIwge8wHQYDVR0O
+BBYEFJKM/O0ViGlwtb3JEci/DLTO/7DaMIG/BgNVHSMEgbcwgbSAFJKM/O0ViGlw
+tb3JEci/DLTO/7DaoYGQpIGNMIGKMQswCQYDVQQGEwJjbjEQMA4GA1UECBMHYmVp
+amluZzEQMA4GA1UEBxMHYmVpamluZzEOMAwGA1UEChMFY25uaWMxDjAMBgNVBAsT
+BWNubmljMRMwEQYDVQQDEwp6aGFuZ2xpa3VuMSIwIAYJKoZIhvcNAQkBFhN6aGFu
+Z2xpa3VuQGNubmljLmNuggkAvCeA0WKTs40wDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQUFAAOBgQBh5N6isMAQAFFD+pbfpppjQlO4vUNcEdzPdeuBFaf9CsX5ZdxV
+jmn1ZuGm6kRzqUPwPSxvCIAY0wuSu1g7YREPAZ3XBVwcg6262iGOA6n7E+nv5PLz
+EuZ1oUg+IfykUIoflKH6xZB4MyPL+EgkMT+i9BrngaXHXF8tEO30YppMiA==
+-----END CERTIFICATE-----
diff --git a/src/bin/cmdctl/tests/testdata/noca-certfile.pem b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
new file mode 100644
index 0000000..5636869
--- /dev/null
+++ b/src/bin/cmdctl/tests/testdata/noca-certfile.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAe6gAwIBAgIRALUIj3nnW5uDE/+fglPvUDwwDQYJKoZIhvcNAQELBQAw
+HjELMAkGA1UEBhMCVVMxDzANBgNVBAMTBkJJTkQxMDAeFw0xMjExMTQxMjQ5MjVa
+Fw0xMzExMTQxMjQ5MjVaMB4xCzAJBgNVBAYTAlVTMQ8wDQYDVQQDEwZCSU5EMTAw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkIOPfs3Aw9kNDu1JqA2w3
+84/n9oUgAwAlHVmuJv7ZDw1MDaIKHjsh3DW09z+nv67GVksI7pFtAw5O4mnTDxpa
+JT0NKzhvYGfe8VdV/hWDogTIdk1QBJNZ2/id8z0h8z5001sARXPf+4mHBJslenH3
+YtZs22BG5RBLULtZ/2Nr7JkdfLlc6D5PCoDG22r1OiFkYVdCWfLDjisVIbSYPBtY
+BlKAIrvbmOtWcaGM+vQAhl0T5N8WRCKhaQH0DEmzQNckkYd7rSECo57KYiuvOdzp
+d+3bWTgGGy2ff0o3LZypv0O5s0TDC2H6hYtN4bUbcChUJbFu9b5sVZaOEVZtUsyD
+AgMBAAGjPzA9MAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgTwMB0GA1UdDgQW
+BBSqGzsEDNs9E7gBL5pD6XVAwUo4DTANBgkqhkiG9w0BAQsFAAOCAQEAMTNB8NCU
+dnLFZ0jNpvecbECkX/OWGlBYU4/CsoNiibwp4CtUYS2A4NFVjWAyuzLSHhRQi0vJ
+CCWLpKL4VTkaDN5Oft42iUhvEXMnriJqpfXHnjCiBwFFSPl5WKfMIaRNK+tF4zbB
+F+FGNEEmYG3t/ni82orDLq4oy+7CoQwzZNzj5yoV6q7O9kLR9OMPNwJrc27A4erB
+7VMRZslSrNA4uA6YhMZl8iEvO1H801ct0zTxawrCihPOZOCSLew35xjztO7d3YH8
+YavOu5kzeu7AgZ2n75H/qU47ZgBjbonn9Osvrct+RIwZuWTB2bDML8JhNaZCq0aA
+TDBC0QWqIYypLg==
+-----END CERTIFICATE-----
diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes
index 90ede92..2fbd000 100644
--- a/src/bin/dbutil/dbutil_messages.mes
+++ b/src/bin/dbutil/dbutil_messages.mes
@@ -53,7 +53,7 @@ inconsistent state, and it is advised to restore it from the backup that was
created when b10-dbutil started.
% DBUTIL_EXECUTE Executing SQL statement: %1
-Debug message; the given SQL statement is executed
+Debug message; the given SQL statement is executed.
% DBUTIL_FILE Database file: %1
The database file that is being checked.
@@ -67,7 +67,7 @@ The given database statement failed to execute. The error is shown in the
message.
% DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
-There were too many command-line arguments to b10-dbutil
+There were too many command-line arguments to b10-dbutil.
% DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
The user aborted the upgrade, and b10-dbutil will now exit.
@@ -95,7 +95,7 @@ again.
% DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1
An unexpected error occurred while b10-dbutil was preparing to upgrade the
-database schema. The error is shown in the message
+database schema. The error is shown in the message.
% DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed
The database schema update was completed successfully.
diff --git a/src/bin/ddns/ddns.py.in b/src/bin/ddns/ddns.py.in
index eaeb06c..094e0ec 100755
--- a/src/bin/ddns/ddns.py.in
+++ b/src/bin/ddns/ddns.py.in
@@ -45,7 +45,7 @@ import os.path
import signal
import socket
-isc.log.init("b10-ddns")
+isc.log.init("b10-ddns", buffer=True)
logger = isc.log.Logger("ddns")
TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
diff --git a/src/bin/dhcp4/Makefile.am b/src/bin/dhcp4/Makefile.am
index e0c97d1..28f08f7 100644
--- a/src/bin/dhcp4/Makefile.am
+++ b/src/bin/dhcp4/Makefile.am
@@ -44,6 +44,7 @@ pkglibexec_PROGRAMS = b10-dhcp4
b10_dhcp4_SOURCES = main.cc
b10_dhcp4_SOURCES += ctrl_dhcp4_srv.cc ctrl_dhcp4_srv.h
+b10_dhcp4_SOURCES += config_parser.cc config_parser.h
b10_dhcp4_SOURCES += dhcp4_log.cc dhcp4_log.h
b10_dhcp4_SOURCES += dhcp4_srv.cc dhcp4_srv.h
@@ -57,12 +58,13 @@ b10_dhcp4_CXXFLAGS = -Wno-unused-parameter
endif
b10_dhcp4_LDADD = $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+b10_dhcp4_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp4_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-
b10_dhcp4dir = $(pkgdatadir)
b10_dhcp4_DATA = dhcp4.spec
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
new file mode 100644
index 0000000..fdb4240
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.cc
@@ -0,0 +1,1765 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config/ccsession.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/dhcp4_log.h>
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/foreach.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <limits>
+#include <iostream>
+#include <vector>
+#include <map>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::data;
+using namespace isc::asiolink;
+
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+/// @brief auxiliary type used for storing element name and its parser
+typedef pair<string, ConstElementPtr> ConfigPair;
+
+/// @brief a factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
+
+/// @brief a collection of factories that creates parsers for specified element names
+typedef std::map<std::string, ParserFactory*> FactoryMap;
+
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief a collection of pools
+///
+/// That type is used as intermediate storage, when pools are parsed, but there is
+/// no subnet object created yet to store them.
+typedef std::vector<Pool4Ptr> PoolStorage;
+
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
+
+/// @brief Global uint32 parameters that will be used as defaults.
+Uint32Storage uint32_defaults;
+
+/// @brief global string parameters that will be used as defaults.
+StringStorage string_defaults;
+
+/// @brief Global storage for options that will be used as defaults.
+OptionStorage option_defaults;
+
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+/// @brief a dummy configuration parser
+///
+/// It is a debugging parser. It does not configure anything,
+/// will accept any configuration and will just print it out
+/// on commit. Useful for debugging existing configurations and
+/// adding new ones.
+class DebugParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param param_name name of the parsed parameter
+ DebugParser(const std::string& param_name)
+ :param_name_(param_name) {
+ }
+
+ /// @brief builds parameter value
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ ///
+ /// @param new_config pointer to the new configuration
+ virtual void build(ConstElementPtr new_config) {
+ std::cout << "Build for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ value_ = new_config;
+ }
+
+ /// @brief pretends to apply the configuration
+ ///
+ /// This is a method required by base class. It pretends to apply the
+ /// configuration, but in fact it only prints the parameter out.
+ ///
+ /// See @ref DhcpConfigParser class for details.
+ virtual void commit() {
+ // Debug message. The whole DebugParser class is used only for parser
+ // debugging, and is not used in production code. It is very convenient
+ // to keep it around. Please do not turn this cout into logger calls.
+ std::cout << "Commit for token: [" << param_name_ << "] = ["
+ << value_->str() << "]" << std::endl;
+ }
+
+ /// @brief factory that constructs DebugParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static DhcpConfigParser* Factory(const std::string& param_name) {
+ return (new DebugParser(param_name));
+ }
+
+private:
+ /// name of the parsed parameter
+ std::string param_name_;
+
+ /// pointer to the actual value of the parameter
+ ConstElementPtr value_;
+};
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
+/// @brief Configuration parser for uint32 parameters
+///
+/// This class is a generic parser that is able to handle any uint32 integer
+/// type. By default it stores the value in external global container
+/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, @ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// @ref dhcpv4ConfigInherit page.
+class Uint32Parser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor for Uint32Parser
+ /// @param param_name name of the configuration parameter being parsed
+ Uint32Parser(const std::string& param_name)
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parses configuration configuration parameter as uint32_t.
+ ///
+ /// @param value pointer to the content of parsed values
+ /// @throw BadValue if supplied value could not be base to uint32_t
+ /// or the parameter name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
+ int64_t check;
+ string x = value->str();
+ try {
+ check = boost::lexical_cast<int64_t>(x);
+ } catch (const boost::bad_lexical_cast &) {
+ isc_throw(BadValue, "Failed to parse value " << value->str()
+ << " as unsigned 32-bit integer.");
+ }
+ if (check > std::numeric_limits<uint32_t>::max()) {
+ isc_throw(BadValue, "Value " << value->str() << "is too large"
+ << " for unsigned 32-bit integer.");
+ }
+ if (check < 0) {
+ isc_throw(BadValue, "Value " << value->str() << "is negative."
+ << " Only 0 or larger are allowed for unsigned 32-bit integer.");
+ }
+
+ // value is small enough to fit
+ value_ = static_cast<uint32_t>(check);
+ }
+
+ /// @brief Stores the parsed uint32_t value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief factory that constructs Uint32Parser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new Uint32Parser(param_name));
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See @ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(Uint32Storage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// pointer to the storage, where parsed value will be stored
+ Uint32Storage* storage_;
+
+ /// name of the parameter to be parsed
+ std::string param_name_;
+
+ /// the actual parsed value
+ uint32_t value_;
+};
+
+/// @brief Configuration parser for string parameters
+///
+/// This class is a generic parser that is able to handle any string
+/// parameter. By default it stores the value in external global container
+/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
+/// in subnet config), it can be pointed to a different storage, using
+/// setStorage() method. This class follows the parser interface, laid out
+/// in its base class, @ref DhcpConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// @ref dhcpv4ConfigInherit page.
+class StringParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor for StringParser
+ /// @param param_name name of the configuration parameter being parsed
+ StringParser(const std::string& param_name)
+ :storage_(&string_defaults), param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief parses parameter value
+ ///
+ /// Parses configuration entry and stores it in storage. See
+ /// @ref setStorage() for details.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(ConstElementPtr value) {
+ value_ = value->str();
+ boost::erase_all(value_, "\"");
+ }
+
+ /// @brief Stores the parsed value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief factory that constructs StringParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new StringParser(param_name));
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See \ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(StringStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// pointer to the storage, where parsed value will be stored
+ StringStorage* storage_;
+
+ /// name of the parameter to be parsed
+ std::string param_name_;
+
+ /// the actual parsed value
+ std::string value_;
+};
+
+
+/// @brief parser for interface list definition
+///
+/// This parser handles Dhcp4/interface entry.
+/// It contains a list of network interfaces that the server listens on.
+/// In particular, it can contain an entry called "all" or "any" that
+/// designates all interfaces.
+///
+/// It is useful for parsing Dhcp4/interface parameter.
+class InterfaceListConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ /// As this is a dedicated parser, it must be used to parse
+ /// "interface" parameter only. All other types will throw exception.
+ ///
+ /// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
+ InterfaceListConfigParser(const std::string& param_name) {
+ if (param_name != "interface") {
+ isc_throw(BadValue, "Internal error. Interface configuration "
+ "parser called for the wrong parameter: " << param_name);
+ }
+ }
+
+ /// @brief parses parameters value
+ ///
+ /// Parses configuration entry (list of parameters) and adds each element
+ /// to the interfaces list.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(ConstElementPtr value) {
+ BOOST_FOREACH(ConstElementPtr iface, value->listValue()) {
+ interfaces_.push_back(iface->str());
+ }
+ }
+
+ /// @brief commits interfaces list configuration
+ virtual void commit() {
+ /// @todo: Implement per interface listening. Currently always listening
+ /// on all interfaces.
+ }
+
+ /// @brief factory that constructs InterfaceListConfigParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new InterfaceListConfigParser(param_name));
+ }
+
+private:
+ /// contains list of network interfaces
+ vector<string> interfaces_;
+};
+
+/// @brief parser for pool definition
+///
+/// This parser handles pool definitions, i.e. a list of entries of one
+/// of two syntaxes: min-max and prefix/len. Pool4 objects are created
+/// and stored in chosen PoolStorage container.
+///
+/// As there are no default values for pool, setStorage() must be called
+/// before build(). Otherwise exception will be thrown.
+///
+/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
+class PoolParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor.
+ PoolParser(const std::string& /*param_name*/)
+ :pools_(NULL) {
+ // ignore parameter name, it is always Dhcp4/subnet4[X]/pool
+ }
+
+ /// @brief parses the actual list
+ ///
+ /// This method parses the actual list of interfaces.
+ /// No validation is done at this stage, everything is interpreted as
+ /// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw InvalidOperation if storage was not specified (setStorage() not called)
+ /// @throw DhcpConfigError when pool parsing fails
+ void build(ConstElementPtr pools_list) {
+ // setStorage() should have been called before build
+ if (!pools_) {
+ isc_throw(InvalidOperation, "Parser logic error. No pool storage set,"
+ " but pool parser asked to parse pools");
+ }
+
+ BOOST_FOREACH(ConstElementPtr text_pool, pools_list->listValue()) {
+
+ // That should be a single pool representation. It should contain
+ // text is form prefix/len or first - last. Note that spaces
+ // are allowed
+ string txt = text_pool->stringValue();
+
+ // first let's remove any whitespaces
+ boost::erase_all(txt, " "); // space
+ boost::erase_all(txt, "\t"); // tabulation
+
+ // Is this prefix/len notation?
+ size_t pos = txt.find("/");
+ if (pos != string::npos) {
+ IOAddress addr("::");
+ uint8_t len = 0;
+ try {
+ addr = IOAddress(txt.substr(0, pos));
+
+ // start with the first character after /
+ string prefix_len = txt.substr(pos + 1);
+
+ // It is lexical cast to int and then downcast to uint8_t.
+ // Direct cast to uint8_t (which is really an unsigned char)
+ // will result in interpreting the first digit as output
+ // value and throwing exception if length is written on two
+ // digits (because there are extra characters left over).
+
+ // No checks for values over 128. Range correctness will
+ // be checked in Pool4 constructor.
+ len = boost::lexical_cast<int>(prefix_len);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ Pool4Ptr pool(new Pool4(addr, len));
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ // Is this min-max notation?
+ pos = txt.find("-");
+ if (pos != string::npos) {
+ // using min-max notation
+ IOAddress min(txt.substr(0,pos));
+ IOAddress max(txt.substr(pos + 1));
+
+ Pool4Ptr pool(new Pool4(min, max));
+
+ local_pools_.push_back(pool);
+ continue;
+ }
+
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
+ << text_pool->stringValue() <<
+ ". Does not contain - (for min-max) nor / (prefix/len)");
+ }
+ }
+
+ /// @brief sets storage for value of this parameter
+ ///
+ /// See \ref dhcpv4ConfigInherit for details.
+ ///
+ /// @param storage pointer to the storage container
+ void setStorage(PoolStorage* storage) {
+ pools_ = storage;
+ }
+
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
+
+ /// @brief factory that constructs PoolParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new PoolParser(param_name));
+ }
+
+private:
+ /// @brief pointer to the actual Pools storage
+ ///
+ /// That is typically a storage somewhere in Subnet parser
+ /// (an upper level parser).
+ PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
+};
+
+/// @brief Parser for option data value.
+///
+/// This parser parses configuration entries that specify value of
+/// a single option. These entries include option name, option code
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Class constructor.
+ OptionDataParser(const std::string&)
+ : options_(NULL),
+ // initialize option to NULL ptr
+ option_descriptor_(false) { }
+
+ /// @brief Parses the single option data.
+ ///
+ /// This method parses the data of a single option from the configuration.
+ /// The option data includes option name, option code and data being
+ /// carried by this option. Eventually it creates the instance of the
+ /// option.
+ ///
+ /// @warning setStorage must be called with valid storage pointer prior
+ /// to calling this method.
+ ///
+ /// @param option_data_entries collection of entries that define value
+ /// for a particular option.
+ /// @throw DhcpConfigError if invalid parameter specified in
+ /// the configuration.
+ /// @throw isc::InvalidOperation if failed to set storage prior to
+ /// calling build.
+ virtual void build(ConstElementPtr option_data_entries) {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ "parsing option data.");
+ }
+ BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
+ ParserPtr parser;
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
+ boost::shared_ptr<StringParser>
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
+ if (name_parser) {
+ name_parser->setStorage(&string_values_);
+ parser = name_parser;
+ }
+ } else if (param.first == "code") {
+ boost::shared_ptr<Uint32Parser>
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
+ if (value_parser) {
+ value_parser->setStorage(&boolean_values_);
+ parser = value_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError,
+ "Parser error: option-data parameter not supported: "
+ << param.first);
+ }
+ parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
+ }
+ // Try to create the option instance.
+ createOption();
+ }
+
+ /// @brief Commits option value.
+ ///
+ /// This function adds a new option to the storage or replaces an existing option
+ /// with the same code.
+ ///
+ /// @throw isc::InvalidOperation if failed to set pointer to storage or failed
+ /// to call build() prior to commit. If that happens data in the storage
+ /// remain un-modified.
+ virtual void commit() {
+ if (options_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
+ "commiting option data.");
+ } else if (!option_descriptor_.option) {
+ // Before we can commit the new option should be configured. If it is not
+ // than somebody must have called commit() before build().
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
+ " thus there is nothing to commit. Has build() been called?");
+ }
+ uint16_t opt_type = option_descriptor_.option->getType();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+ // Try to find options with the particular option code in the main
+ // storage. If found, remove these options because they will be
+ // replaced with new one.
+ Subnet::OptionContainerTypeRange range =
+ idx.equal_range(opt_type);
+ if (std::distance(range.first, range.second) > 0) {
+ idx.erase(range.first, range.second);
+ }
+ // Append new option to the main storage.
+ options_->addItem(option_descriptor_, option_space_);
+ }
+
+ /// @brief Set storage for the parser.
+ ///
+ /// Sets storage for the parser. This storage points to the
+ /// vector of options and is used by multiple instances of
+ /// OptionDataParser. Each instance creates exactly one object
+ /// of dhcp::Option or derived type and appends it to this
+ /// storage.
+ ///
+ /// @param storage pointer to the options storage
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+private:
+
+ /// @brief Create option instance.
+ ///
+ /// Creates an instance of an option and adds it to the provided
+ /// options storage. If the option data parsed by \ref build function
+ /// are invalid or insufficient this function emits an exception.
+ ///
+ /// @warning this function does not check if options_ storage pointer
+ /// is intitialized but this check is not needed here because it is done
+ /// in the \ref build function.
+ ///
+ /// @throw DhcpConfigError if parameters provided in the configuration
+ /// are invalid.
+ void createOption() {
+ // Option code is held in the uint32_t storage but is supposed to
+ // be uint16_t value. We need to check that value in the configuration
+ // does not exceed range of uint16_t and is not zero.
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
+ if (option_code == 0) {
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
+ << " be equal to zero. Option code '0' is reserved in"
+ << " DHCPv4.");
+ } else if (option_code > std::numeric_limits<uint16_t>::max()) {
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
+ << " exceed " << std::numeric_limits<uint16_t>::max());
+ }
+ // Check that the option name has been specified, is non-empty and does not
+ // contain spaces.
+ // @todo possibly some more restrictions apply here?
+ std::string option_name = getParam<std::string>("name", string_values_);
+ if (option_name.empty()) {
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
+ << " empty");
+ } else if (option_name.find(" ") != std::string::npos) {
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
+ << " spaces");
+ }
+
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V4, option_code);
+
+ } else if (option_space == "dhcp6") {
+ isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
+ << " for DHCPv6 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
+ // Get option data from the configuration database ('data' field).
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
+ // Transform string of hexadecimal digits into binary format.
+ std::vector<uint8_t> binary;
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
+ }
+
+ OptionPtr option;
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
+ // @todo We have a limited set of option definitions intiialized at the moment.
+ // In the future we want to initialize option definitions for all options.
+ // Consequently an error will be issued if an option definition does not exist
+ // for a particular option code. For now it is ok to create generic option
+ // if definition does not exist.
+ OptionPtr option(new Option(Option::V4, static_cast<uint16_t>(option_code),
+ binary));
+ // The created option is stored in option_descriptor_ class member until the
+ // commit stage when it is inserted into the main storage. If an option with the
+ // same code exists in main storage already the old option is replaced.
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } else {
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
+ try {
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V4, option_code, data_tokens) :
+ def->optionFactory(Option::V4, option_code, binary);
+ Subnet::OptionDescriptor desc(option, false);
+ option_descriptor_.option = option;
+ option_descriptor_.persistent = false;
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
+ << ex.what());
+ }
+ }
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
+ }
+
+ /// Storage for uint32 values (e.g. option code).
+ Uint32Storage uint32_values_;
+ /// Storage for string values (e.g. option name or data).
+ StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Pointer to options storage. This storage is provided by
+ /// the calling class and is shared by all OptionDataParser objects.
+ OptionStorage* options_;
+ /// Option descriptor holds newly configured option.
+ Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
+};
+
+/// @brief Parser for option data values within a subnet.
+///
+/// This parser iterates over all entries that define options
+/// data for a particular subnet and creates a collection of options.
+/// If parsing is successful, all these options are added to the Subnet
+/// object.
+class OptionDataListParser : public DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Unless otherwise specified, parsed options will be stored in
+ /// a global option container (option_default). That storage location
+ /// is overriden on a subnet basis.
+ OptionDataListParser(const std::string&)
+ : options_(&option_defaults), local_options_() { }
+
+ /// @brief Parses entries that define options' data for a subnet.
+ ///
+ /// This method iterates over all entries that define option data
+ /// for options within a single subnet and creates options' instances.
+ ///
+ /// @param option_data_list pointer to a list of options' data sets.
+ /// @throw DhcpConfigError if option parsing failed.
+ void build(ConstElementPtr option_data_list) {
+ BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
+ boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
+ // options_ member will hold instances of all options thus
+ // each OptionDataParser takes it as a storage.
+ parser->setStorage(&local_options_);
+ // Build the instance of a single option.
+ parser->build(option_value);
+ // Store a parser as it will be used to commit.
+ parsers_.push_back(parser);
+ }
+ }
+
+ /// @brief Set storage for option instances.
+ ///
+ /// @param storage pointer to options storage.
+ void setStorage(OptionStorage* storage) {
+ options_ = storage;
+ }
+
+
+ /// @brief Commit all option values.
+ ///
+ /// This function invokes commit for all option values.
+ void commit() {
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+ // Parsing was successful and we have all configured
+ // options in local storage. We can now replace old values
+ // with new values.
+ std::swap(local_options_, *options_);
+ }
+
+ /// @brief Create DhcpDataListParser object
+ ///
+ /// @param param_name param name.
+ ///
+ /// @return DhcpConfigParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDataListParser(param_name));
+ }
+
+ /// Intermediate option storage. This storage is used by
+ /// lower level parsers to add new options. Values held
+ /// in this storage are assigned to main storage (options_)
+ /// if overall parsing was successful.
+ OptionStorage local_options_;
+ /// Pointer to options instances storage.
+ OptionStorage* options_;
+ /// Collection of parsers;
+ ParserCollection parsers_;
+};
+
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
+/// @brief this class parses a single subnet
+///
+/// This class parses the whole subnet definition. It creates parsers
+/// for received configuration parameters as needed.
+class Subnet4ConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ Subnet4ConfigParser(const std::string& ) {
+ // The parameter should always be "subnet", but we don't check here
+ // against it in case someone wants to reuse this parser somewhere.
+ }
+
+ /// @brief parses parameter value
+ ///
+ /// @param subnet pointer to the content of subnet definition
+ void build(ConstElementPtr subnet) {
+
+ BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+ ParserPtr parser(createSubnet4ConfigParser(param.first));
+ // The actual type of the parser is unknown here. We have to discover
+ // the parser type here to invoke the corresponding setStorage function
+ // on it. We discover parser type by trying to cast the parser to various
+ // parser types and checking which one was successful. For this one
+ // a setStorage and build methods are invoked.
+
+ // Try uint32 type parser.
+ if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+ param.second) &&
+ // Try string type parser.
+ !buildParser<StringParser, StringStorage >(parser, string_values_,
+ param.second) &&
+ // Try pool parser.
+ !buildParser<PoolParser, PoolStorage >(parser, pools_,
+ param.second) &&
+ // Try option data parser.
+ !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+ param.second)) {
+ // Appropriate parsers are created in the createSubnet6ConfigParser
+ // and they should be limited to those that we check here for. Thus,
+ // if we fail to find a matching parser here it is a programming error.
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
+ }
+ }
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Create a subnet.
+ createSubnet();
+ }
+
+ /// @brief commits received configuration.
+ ///
+ /// This method does most of the configuration. Many other parsers are just
+ /// storing the values that are actually consumed here. Pool definitions
+ /// created in other parsers are used here and added to newly created Subnet4
+ /// objects. Subnet4 are then added to DHCP CfgMgr.
+ /// @throw DhcpConfigError if there are any issues encountered during commit
+ void commit() {
+ if (subnet_) {
+ CfgMgr::instance().addSubnet4(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
+ StringStorage::const_iterator it = string_values_.find("subnet");
+ if (it == string_values_.end()) {
+ isc_throw(DhcpConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+ // Remove any spaces or tabs.
+ string subnet_txt = it->second;
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(DhcpConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << it->second);
+ }
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
+ IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
+ Triplet<uint32_t> t1 = getParam("renew-timer");
+ Triplet<uint32_t> t2 = getParam("rebind-timer");
+ Triplet<uint32_t> valid = getParam("valid-lifetime");
+
+ /// @todo: Convert this to logger once the parser is working reliably
+ stringstream tmp;
+ tmp << addr.toText() << "/" << (int)len
+ << " with params t1=" << t1 << ", t2=" << t2 << ", valid=" << valid;
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
+
+ subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
+
+ for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+ subnet_->addPool(*it);
+ }
+
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+
+ // Check all global options and add them to the subnet object if
+ // they have been configured in the global scope. If they have been
+ // configured in the subnet scope we don't add global option because
+ // the one configured in the subnet scope always takes precedence.
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
+ }
+ }
+ }
+
+ /// @brief creates parsers for entries in subnet definition
+ ///
+ /// @todo Add subnet-specific things here (e.g. subnet-specific options)
+ ///
+ /// @param config_id name od the entry
+ /// @return parser object for specified entry name
+ /// @throw NotImplemented if trying to create a parser for unknown config element
+ DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DebugParser(config_id);
+
+ isc_throw(NotImplemented,
+ "parser error: Subnet4 parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+ }
+
+ /// @brief Returns value for a given parameter (after using inheritance)
+ ///
+ /// This method implements inheritance. For a given parameter name, it first
+ /// checks if there is a global value for it and overwrites it with specific
+ /// value if such value was defined in subnet.
+ ///
+ /// @param name name of the parameter
+ /// @return triplet with the parameter name
+ /// @throw DhcpConfigError when requested parameter is not present
+ Triplet<uint32_t> getParam(const std::string& name) {
+ uint32_t value = 0;
+ bool found = false;
+ Uint32Storage::iterator global = uint32_defaults.find(name);
+ if (global != uint32_defaults.end()) {
+ value = global->second;
+ found = true;
+ }
+
+ Uint32Storage::iterator local = uint32_values_.find(name);
+ if (local != uint32_values_.end()) {
+ value = local->second;
+ found = true;
+ }
+
+ if (found) {
+ return (Triplet<uint32_t>(value));
+ } else {
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
+ << " missing (no global default and no subnet-"
+ << "specific value)");
+ }
+ }
+
+ /// storage for subnet-specific uint32 values
+ Uint32Storage uint32_values_;
+
+ /// storage for subnet-specific integer values
+ StringStorage string_values_;
+
+ /// storage for pools belonging to this subnet
+ PoolStorage pools_;
+
+ /// storage for options belonging to this subnet
+ OptionStorage options_;
+
+ /// parsers are stored here
+ ParserCollection parsers_;
+
+ /// @brief Pointer to the created subnet object.
+ isc::dhcp::Subnet4Ptr subnet_;
+};
+
+/// @brief this class parses list of subnets
+///
+/// This is a wrapper parser that handles the whole list of Subnet4
+/// definitions. It iterates over all entries and creates Subnet4ConfigParser
+/// for each entry.
+class Subnets4ListConfigParser : public DhcpConfigParser {
+public:
+
+ /// @brief constructor
+ ///
+ Subnets4ListConfigParser(const std::string&) {
+ /// parameter name is ignored
+ }
+
+ /// @brief parses contents of the list
+ ///
+ /// Iterates over all entries on the list and creates Subnet4ConfigParser
+ /// for each entry.
+ ///
+ /// @param subnets_list pointer to a list of IPv4 subnets
+ void build(ConstElementPtr subnets_list) {
+
+ // No need to define FactoryMap here. There's only one type
+ // used: Subnet4ConfigParser
+
+ BOOST_FOREACH(ConstElementPtr subnet, subnets_list->listValue()) {
+ ParserPtr parser(new Subnet4ConfigParser("subnet"));
+ parser->build(subnet);
+ subnets_.push_back(parser);
+ }
+
+ }
+
+ /// @brief commits subnets definitions.
+ ///
+ /// Iterates over all Subnet4 parsers. Each parser contains definitions
+ /// of a single subnet and its parameters and commits each subnet separately.
+ void commit() {
+ // @todo: Implement more subtle reconfiguration than toss
+ // the old one and replace with the new one.
+
+ // remove old subnets
+ CfgMgr::instance().deleteSubnets4();
+
+ BOOST_FOREACH(ParserPtr subnet, subnets_) {
+ subnet->commit();
+ }
+
+ }
+
+ /// @brief Returns Subnet4ListConfigParser object
+ /// @param param_name name of the parameter
+ /// @return Subnets4ListConfigParser object
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new Subnets4ListConfigParser(param_name));
+ }
+
+ /// @brief collection of subnet parsers.
+ ParserCollection subnets_;
+
+};
+
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
+/// @brief creates global parsers
+///
+/// This method creates global parsers that parse global parameters, i.e.
+/// those that take format of Dhcp4/param1, Dhcp4/param2 and so forth.
+///
+/// @param config_id pointer to received global configuration entry
+/// @return parser for specified global DHCPv4 parameter
+/// @throw NotImplemented if trying to create a parser for unknown config element
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
+ FactoryMap factories;
+
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet4"] = Subnets4ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
+
+ FactoryMap::iterator f = factories.find(config_id);
+ if (f == factories.end()) {
+ // Used for debugging only.
+ // return new DebugParser(config_id);
+
+ isc_throw(NotImplemented,
+ "Parser error: Global configuration parameter not supported: "
+ << config_id);
+ }
+ return (f->second(config_id));
+}
+
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
+ if (!config_set) {
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
+ }
+
+ /// @todo: append most essential info here (like "2 new subnets configured")
+ string config_details;
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_START).arg(config_set->str());
+
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
+
+ try {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ if (config_pair.first == "subnet4") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet4");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
+ }
+
+ } catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+ }
+
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+ rollback = true;
+
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ rollback = true;
+
+ }
+ }
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
+ return (answer);
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
+
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
+ return (answer);
+}
+
+const std::map<std::string, uint32_t>& getUint32Defaults() {
+ return (uint32_defaults);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
new file mode 100644
index 0000000..4f1ea32
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.h
@@ -0,0 +1,74 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+#include <cc/data.h>
+#include <stdint.h>
+#include <string>
+
+#ifndef DHCP4_CONFIG_PARSER_H
+#define DHCP4_CONFIG_PARSER_H
+
+/// @todo: This header file and its .cc counterpart are very similar between
+/// DHCPv4 and DHCPv6. They should be merged. A ticket #2355.
+
+namespace isc {
+namespace dhcp {
+
+class Dhcpv4Srv;
+
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
+///
+/// This function parses configuration information stored in @c config_set
+/// and configures the @c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of @c DhcpConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function returns appropriate error code.
+///
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv4 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
+///
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
+///
+/// @param config_set a new configuration (JSON) for DHCPv4 server
+/// @return answer that contains result of reconfiguration
+isc::data::ConstElementPtr
+configureDhcp4Server(Dhcpv4Srv&,
+ isc::data::ConstElementPtr config_set);
+
+
+/// @brief Returns the global uint32_t values storage.
+///
+/// This function must be only used by unit tests that need
+/// to access uint32_t global storage to verify that the
+/// Uint32Parser works as expected.
+///
+/// @return a reference to a global uint32 values storage.
+const std::map<std::string, uint32_t>& getUint32Defaults();
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // DHCP4_CONFIG_PARSER_H
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index b02bf72..435a25e 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,11 +19,15 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
+#include <dhcp4/config_parser.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
+#include <cassert>
+#include <iostream>
#include <cassert>
#include <iostream>
@@ -46,8 +50,14 @@ ConstElementPtr
ControlledDhcpv4Srv::dhcp4ConfigHandler(ConstElementPtr new_config) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_UPDATE)
.arg(new_config->str());
- ConstElementPtr answer = isc::config::createAnswer(0,
- "Thank you for sending config.");
+ if (server_) {
+ return (configureDhcp4Server(*server_, new_config));
+ }
+
+ // That should never happen as we install config_handler after we instantiate
+ // the server.
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ "Configuration rejected, server is during startup/shutdown phase.");
return (answer);
}
@@ -100,10 +110,22 @@ void ControlledDhcpv4Srv::establishSession() {
.arg(specfile);
cc_session_ = new Session(io_service_.get_io_service());
config_session_ = new ModuleCCSession(specfile, *cc_session_,
- dhcp4ConfigHandler,
+ NULL,
dhcp4CommandHandler, false);
config_session_->start();
+ // We initially create ModuleCCSession() without configHandler, as
+ // the session module is too eager to send partial configuration.
+ // We want to get the full configuration, so we explicitly call
+ // getFullConfig() and then pass it to our configHandler.
+ config_session_->setConfigHandler(dhcp4ConfigHandler);
+
+ try {
+ configureDhcp4Server(*this, config_session_->getFullConfig());
+ } catch (const DhcpConfigError& ex) {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
+ }
+
/// Integrate the asynchronous I/O model of BIND 10 configuration
/// control with the "select" model of the DHCP server. This is
/// fully explained in \ref dhcpv4Session.
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 9fd7668..9bd261c 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -66,7 +66,9 @@ public:
/// @brief Session callback, processes received commands.
///
- /// @param command_id text represenation of the command (e.g. "shutdown")
+ /// @param command Text represenation of the command (e.g. "shutdown")
+ /// @param args Optional parameters
+ /// @param command text represenation of the command (e.g. "shutdown")
/// @param args optional parameters
///
/// @return status of the command
diff --git a/src/bin/dhcp4/dhcp4.dox b/src/bin/dhcp4/dhcp4.dox
new file mode 100644
index 0000000..05f1670
--- /dev/null
+++ b/src/bin/dhcp4/dhcp4.dox
@@ -0,0 +1,68 @@
+/**
+ @page dhcp4 DHCPv4 Server Component
+
+BIND10 offers DHCPv4 server implementation. It is implemented as
+b10-dhcp4 component. Its primary code is located in
+isc::dhcp::Dhcpv4Srv class. It uses \ref libdhcp extensively,
+especially isc::dhcp::Pkt4, isc::dhcp::Option and
+isc::dhcp::IfaceMgr classes. Currently this code offers skeleton
+functionality, i.e. it is able to receive and process incoming
+requests and trasmit responses. However, it does not have database
+management, so it returns only one, hardcoded lease to whoever asks
+for it.
+
+DHCPv4 server component does not support direct traffic (relayed
+only), as support for transmission to hosts without IPv4 address
+assigned is not implemented in IfaceMgr yet.
+
+ at section dhcpv4Session BIND10 message queue integration
+
+DHCPv4 server component is now integrated with BIND10 message queue.
+The integration is performed by establishSession() and disconnectSession()
+functions in isc::dhcp::ControlledDhcpv4Srv class. main() method deifined
+in the src/bin/dhcp4/main.cc file instantiates isc::dhcp::ControlledDhcpv4Srv
+class that establishes connection with msgq and install necessary handlers
+for receiving commands and configuration updates. It is derived from
+a base isc::dhcp::Dhcpv4Srv class that implements DHCPv4 server functionality,
+without any controlling mechanisms.
+
+ControlledDhcpv4Srv instantiates several components to make management
+session possible. In particular, isc::cc::Session cc_session
+object uses ASIO for establishing connection. It registers its socket
+in isc::asiolink::IOService io_service object. Typically, other components
+(e.g. auth or resolver) that use ASIO for their communication, register their
+other sockets in the
+same io_service and then just call io_service.run() method that does
+not return, until one of the callback decides that it is time to shut down
+the whole component cal calls io_service.stop(). DHCPv4 works in a
+different way. It does receive messages using select()
+(see isc::dhcp::IfaceMgr::receive4()), which is incompatible with ASIO.
+To solve this problem, socket descriptor is extracted from cc_session
+object and is passed to IfaceMgr by using isc::dhcp::IfaceMgr::set_session_socket().
+IfaceMgr then uses this socket in its select() call. If there is some
+data to be read, it calls registered callback that is supposed to
+read and process incoming data.
+
+This somewhat complicated approach is needed for a simple reason. In
+embedded deployments there will be no message queue. Not referring directly
+to anything related to message queue in isc::dhcp::Dhcpv4Srv and
+isc::dhcp::IfaceMgr classes brings in two benefits. First, the can
+be used with and without message queue. Second benefit is related to the
+first one: \ref libdhcp is supposed to be simple and robust and not require
+many dependencies. One notable example of a use case that benefits from
+this approach is a perfdhcp tool. Finally, the idea is that it should be
+possible to instantiate Dhcpv4Srv object directly, thus getting a server
+that does not support msgq. That is useful for embedded environments.
+It may also be useful in validation.
+
+ at section dhcpv4ConfigParser Configuration Parser in DHCPv4
+
+This parser follows exactly the same logic as its DHCPv6 counterpart.
+See \ref dhcpv6ConfigParser.
+
+ at section dhcpv4ConfigInherit DHCPv4 configuration inheritance
+
+Configuration inheritance in DHCPv4 follows exactly the same logic as its DHCPv6
+counterpart. See \ref dhcpv6ConfigInherit.
+
+*/
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 7584b48..c2b755c 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -4,9 +4,217 @@
"module_description": "DHCPv4 server daemon",
"config_data": [
{ "item_name": "interface",
- "item_type": "string",
+ "item_type": "list",
"item_optional": false,
- "item_default": "eth0"
+ "item_default": [ "all" ],
+ "list_item_spec":
+ {
+ "item_name": "interface_name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "all"
+ }
+ } ,
+
+ { "item_name": "renew-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1000
+ },
+
+ { "item_name": "rebind-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 2000
+ },
+
+ { "item_name": "valid-lifetime",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 4000
+ },
+
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
+ { "item_name": "option-data",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-data",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0
+ },
+ { "item_name": "data",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
+ } ]
+ }
+ },
+
+ { "item_name": "subnet4",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-subnet4",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+
+ { "item_name": "subnet",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "renew-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 1000
+ },
+
+ { "item_name": "rebind-timer",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 2000
+ },
+
+ { "item_name": "valid-lifetime",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 7200
+ },
+ { "item_name": "pool",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ },
+
+ { "item_name": "option-data",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-data",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ {
+ "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0
+ },
+ {
+ "item_name": "data",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
+ } ]
+ }
+ } ]
+ }
}
],
"commands": [
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 392b332..fc47823 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,10 +26,62 @@ to establish a session with the BIND 10 control channel.
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv4 DHCP server.
+% DHCP4_CONFIG_LOAD_FAIL failed to load configuration: %1
+This critical error message indicates that the initial DHCPv4
+configuration has failed. The server will start, but nothing will be
+served until the configuration has been corrected.
+
+% DHCP4_CONFIG_NEW_SUBNET A new subnet has been added to configuration: %1
+This is an informational message reporting that the configuration has
+been extended to include the specified IPv4 subnet.
+
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
+This warning message is issued on an attempt to configure multiple options
+with the same option code for a particular subnet. Adding multiple options
+is uncommon for DHCPv4, but is not prohibited.
+
% DHCP4_CONFIG_UPDATE updated configuration received: %1
A debug message indicating that the IPv4 DHCP server has received an
updated configuration from the BIND 10 configuration system.
+% DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
+This is a debug message that is issued every time the server receives a
+configuration. That happens at start up and also when a server configuration
+change is committed by the administrator.
+
+% DHCP4_CONFIG_COMPLETE DHCPv4 server has completed configuration: %1
+This is an informational message announcing the successful processing of a
+new configuration. It is output during server startup, and when an updated
+configuration is committed by the administrator. Additional information
+may be provided.
+
+% DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
+This informational message is printed every time DHCPv4 server is started
+and gives both the type and name of the database being used to store
+lease and other information.
+
+% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
+This debug message indicates that the server successfully advertised
+a lease. It is up to the client to choose one server out of othe advertised
+and continue allocation with that server. This is a normal behavior and
+indicates successful operation.
+
+% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
+This message indicates that the server has failed to offer a lease to
+the specified client after receiving a DISCOVER message from it. There are
+many possible reasons for such a failure.
+
+% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
+This debug message indicates that the server successfully granted a lease
+in response to client's REQUEST message. This is a normal behavior and
+incicates successful operation.
+
+% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
+This message indicates that the server failed to grant a lease to the
+specified client after receiving a REQUEST message from it. There are many
+possible reasons for such a failure. Additional messages will indicate the
+reason.
+
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv4 DHCP server but it is not running.
@@ -50,7 +102,7 @@ may well be a valid DHCP packet, just a type not expected by the server
% DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv4 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
@@ -66,6 +118,43 @@ to be a programming error: please raise a bug report.
% DHCP4_QUERY_DATA received packet type %1, data is <%2>
A debug message listing the data received from the client.
+% DHCP4_RELEASE address %1 belonging to client-id %2, hwaddr %3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP4_RELEASE_EXCEPTION exception %1 while trying to release address %2
+This message is output when an error was encountered during an attempt
+to process a RELEASE message. The error will not affect the client,
+which does not expect any response from the server for RELEASE
+messages. Depending on the nature of problem, it may affect future
+server operation.
+
+% DHCP4_RELEASE_FAIL failed to remove lease for address %1 for duid %2, hwaddr %3
+This error message indicates that the software failed to remove a
+lease from the lease database. It is probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP4_RELEASE_FAIL_NO_LEASE client (client-id %2) tried to release address %1, but there is no lease for such address.
+This warning message is printed when client attempts to release a lease,
+but no such lease is known to the server.
+
+% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID client (client-id %2) tried to release address %1, but it belongs to client (client-id %3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP4_RELEASE_FAIL_WRONG_HWADDR client (client-id %2) tried to release address %1, but sent from a wrong hardware address (%3)
+This warning message indicates that client tried to release an address
+that does belong to it, but the lease information was associated with
+a different hardware address. One possible reason for using different
+hardware address is that a cloned virtual machine was not updated and
+both clones use the same client-id.
+
% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
A debug message listing the data returned to the client.
@@ -106,3 +195,13 @@ processed any command-line switches and is starting.
This is a debug message issued during the IPv4 DHCP server startup.
It lists some information about the parameters with which the server
is running.
+
+% DHCP4_SUBNET_SELECTED the %1 subnet was selected for client assignment
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment. Subnet selection is one of the early
+steps in the processing of incoming client message.
+
+% DHCP4_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src: %1, type: %2
+This warning message is output when a packet was received from a subnet
+for which the DHCPv4 server has not been configured. The most probable
+cause is a misconfiguration of the server.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 59edc01..88ccfea 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -16,9 +16,19 @@
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
#include <dhcp/pkt4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/addr_utilities.h>
using namespace isc;
using namespace isc::asiolink;
@@ -28,26 +38,35 @@ using namespace std;
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
const std::string HARDCODED_SERVER_ID = "192.0.2.1";
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
IfaceMgr::instance();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
- IfaceMgr::instance().openSockets4(port);
+ if (port) {
+ // open sockets only if port is non-zero. Port 0 is used
+ // for non-socket related testing.
+ IfaceMgr::instance().openSockets4(port);
+ }
setServerID();
+ // Instantiate LeaseMgr
+ LeaseMgrFactory::create(dbconfig);
+ LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
+ .arg(LeaseMgrFactory::instance().getType())
+ .arg(LeaseMgrFactory::instance().getName());
+
+ // Instantiate allocation engine
+ alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
+
} catch (const std::exception &e) {
LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
shutdown_ = true;
@@ -167,18 +186,11 @@ Dhcpv4Srv::run() {
void
Dhcpv4Srv::setServerID() {
- /// TODO implement this for real once interface detection (ticket 1237)
- /// is done. Use hardcoded server-id for now.
-
-#if 0
- // uncomment this once ticket 1350 is merged.
- IOAddress srvId("127.0.0.1");
- serverid_ = OptionPtr(
- new Option4AddrLst(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, srvId));
-#endif
+ /// @todo: implement this for real (see ticket #2588)
+ serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ IOAddress(HARDCODED_SERVER_ID)));
}
-
void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setIface(question->getIface());
answer->setIndex(question->getIndex());
@@ -188,9 +200,7 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setHops(question->getHops());
// copy MAC address
- vector<uint8_t> mac(question->getChaddr(),
- question->getChaddr() + Pkt4::MAX_CHADDR_LEN);
- answer->setHWAddr(question->getHtype(), question->getHlen(), mac);
+ answer->setHWAddr(question->getHWAddr());
// relay address
answer->setGiaddr(question->getGiaddr());
@@ -203,21 +213,21 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setRemoteAddr(question->getRemoteAddr());
}
+ // Let's copy client-id to response. See RFC6842.
+ OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (client_id) {
+ answer->addOption(client_id);
+ }
}
void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
OptionPtr opt;
// add Message Type Option (type 53)
- std::vector<uint8_t> tmp;
- tmp.push_back(static_cast<uint8_t>(msg_type));
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
- msg->addOption(opt);
+ msg->setType(msg_type);
// DHCP Server Identifier (type 54)
- opt = OptionPtr
- (new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
- msg->addOption(opt);
+ msg->addOption(getServerID());
// more options will be added here later
}
@@ -237,25 +247,109 @@ void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
msg->addOption(opt);
}
-void Dhcpv4Srv::tryAssignLease(Pkt4Ptr& msg) {
- OptionPtr opt;
+void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+
+ // We need to select a subnet the client is connected in.
+ Subnet4Ptr subnet = selectSubnet(question);
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(serverReceivedPacketName(question->getType()));
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ return;
+ }
- // TODO: Implement actual lease assignment here
- msg->setYiaddr(IOAddress(HARDCODED_LEASE));
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
+ .arg(subnet->toText());
- // IP Address Lease time (type 51)
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
- opt->setUint32(HARDCODED_LEASE_TIME);
- msg->addOption(opt);
- // TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
+ // Get client-id option
+ ClientIdPtr client_id;
+ OptionPtr opt = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+ // client-id is not mandatory in DHCPv4
+
+ IOAddress hint = question->getYiaddr();
+
+ HWAddrPtr hwaddr = question->getHWAddr();
+
+ // "Fake" allocation is processing of DISCOVER message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = (question->getType() == DHCPDISCOVER);
+
+ // Use allocation engine to pick a lease for this client. Allocation engine
+ // will try to honour the hint, but it is just a hint - some other address
+ // may be used instead. If fake_allocation is set to false, the lease will
+ // be inserted into the LeaseMgr as well.
+ Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
+ hint, fake_allocation);
+
+ if (lease) {
+ // We have a lease! Let's set it in the packet and send it back to
+ // the client.
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT:DHCP4_LEASE_ALLOC)
+ .arg(lease->addr_.toText())
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)");
+
+ answer->setYiaddr(lease->addr_);
+
+ // IP Address Lease time (type 51)
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
+ opt->setUint32(lease->valid_lft_);
+ answer->addOption(opt);
+
+ // @todo: include real router information here
+ // Router (type 3)
+ opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
+ answer->addOption(opt);
+
+ // Subnet mask (type 1)
+ answer->addOption(getNetmaskOption(subnet));
+
+ // @todo: send renew timer option (T1, option 58)
+ // @todo: send rebind timer option (T2, option 59)
- // Subnet mask (type 1)
- opt = OptionPtr(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
- msg->addOption(opt);
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure. The only thing left is to insert
+ // status code to pass the sad news to the client.
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT_FAIL:DHCP4_LEASE_ALLOC_FAIL)
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)")
+ .arg(hint.toText());
+
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ }
+}
- // Router (type 3)
- opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
- msg->addOption(opt);
+OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+ uint32_t netmask = getNetmask4(subnet->get().second);
+
+ OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
+ DHO_SUBNET_MASK, netmask));
+
+ return (opt);
}
Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
@@ -266,7 +360,7 @@ Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(offer);
- tryAssignLease(offer);
+ assignLease(discover, offer);
return (offer);
}
@@ -279,13 +373,77 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(ack);
- tryAssignLease(ack);
+ assignLease(request, ack);
return (ack);
}
void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
- /// TODO: Implement this.
+
+ // Try to find client-id
+ ClientIdPtr client_id;
+ OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+
+ try {
+ // Do we have a lease for that particular address?
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getYiaddr());
+
+ if (!lease) {
+ // No such lease - bogus release
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
+ .arg(release->getYiaddr().toText())
+ .arg(release->getHWAddr()->toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)");
+ return;
+ }
+
+ // Does the hardware address match? We don't want one client releasing
+ // second client's leases.
+ if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
+ // @todo: Print hwaddr from lease as part of ticket #2589
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ return;
+ }
+
+ // Does the lease have client-id info? If it has, then check it with what
+ // the client sent us.
+ if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id->toText())
+ .arg(lease->client_id_->toText());
+ return;
+ }
+
+ // Ok, hw and client-id match - let's release the lease.
+ if (LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+
+ // Release successful - we're done here
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ } else {
+
+ // Release failed -
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ }
+ } catch (const isc::Exception& ex) {
+ // Rethrow the exception with a bit more data.
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_EXCEPTION)
+ .arg(ex.what())
+ .arg(release->getYiaddr());
+ }
+
}
void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
@@ -327,3 +485,42 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
}
return (UNKNOWN);
}
+
+Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+
+ // Is this relayed message?
+ IOAddress relay = question->getGiaddr();
+ if (relay.toText() == "0.0.0.0") {
+
+ // Yes: Use relay address to select subnet
+ return (CfgMgr::instance().getSubnet4(relay));
+ } else {
+
+ // No: Use client's address to select subnet
+ return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+ }
+}
+
+void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
+ OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_id) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
+ << "received in " << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case MANDATORY:
+ if (!server_id) {
+ isc_throw(RFCViolation, "Server-id option was expected, but not "
+ " received in message "
+ << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case OPTIONAL:
+ // do nothing here
+ ;
+ }
+}
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 48ccde6..53401c5 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -18,14 +18,16 @@
#include <dhcp/dhcp4.h>
#include <dhcp/pkt4.h>
#include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/alloc_engine.h>
#include <boost/noncopyable.hpp>
#include <iostream>
namespace isc {
-
namespace dhcp {
+
/// @brief DHCPv4 server service.
///
/// This singleton class represents DHCPv4 server. It contains all
@@ -36,7 +38,7 @@ namespace dhcp {
/// appropriate responses.
///
/// This class does not support any controlling mechanisms directly.
-/// See derived \ref ControlledDhcv4Srv class for support for
+/// See the derived \ref ControlledDhcpv4Srv class for support for
/// command and configuration updates over msgq.
///
/// For detailed explanation or relations between main(), ControlledDhcpv4Srv,
@@ -44,6 +46,14 @@ namespace dhcp {
class Dhcpv4Srv : public boost::noncopyable {
public:
+
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
+
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv4 server.
@@ -54,7 +64,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// for testing purposes.
///
/// @param port specifies port number to listen on
- Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
+ /// @param dbconfig Lease manager configuration string. The default
+ /// of the "memfile" manager is used for testing.
+ Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -82,6 +95,8 @@ class Dhcpv4Srv : public boost::noncopyable {
/// As the operation of the method does not depend on any server state, it
/// is declared static.
///
+ /// @todo: This should be named static Pkt4::getName()
+ ///
/// @param type DHCPv4 packet type
///
/// @return Pointer to "const" string containing the packet name.
@@ -90,6 +105,17 @@ class Dhcpv4Srv : public boost::noncopyable {
static const char* serverReceivedPacketName(uint8_t type);
protected:
+
+ /// @brief verifies if specified packet meets RFC requirements
+ ///
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
+ ///
+ /// @param pkt packet to be checked
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
+
/// @brief Processes incoming DISCOVER and returns response.
///
/// Processes received DISCOVER message and verifies that its sender
@@ -156,11 +182,19 @@ protected:
/// client and assigning it. Options corresponding to the lease
/// are added to specific message.
///
- /// Note: Lease manager is not implemented yet, so this method
- /// used fixed, hardcoded lease.
+ /// @param question DISCOVER or REQUEST message from client
+ /// @param answer OFFER or ACK/NAK message (lease options will be added here)
+ void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
+ /// @brief Attempts to renew received addresses
+ ///
+ /// Attempts to renew existing lease. This typically includes finding a lease that
+ /// corresponds to the received address. If no such lease is found, a status code
+ /// response is generated.
///
- /// @param msg OFFER or ACK message (lease options will be added here)
- void tryAssignLease(Pkt4Ptr& msg);
+ /// @param renew client's message asking for renew
+ /// @param reply server's response (ACK or NAK)
+ void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
/// @brief Appends default options to a message
///
@@ -184,12 +218,33 @@ protected:
// previously stored configuration and no network interfaces available)
void setServerID();
+ /// @brief Selects a subnet for a given client's packet.
+ ///
+ /// @param question client's message
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
+
/// server DUID (to be sent in server-identifier option)
OptionPtr serverid_;
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;
+
+ private:
+
+ /// @brief Constructs netmask option based on subnet4
+ /// @param subnet subnet for which the netmask will be calculated
+ ///
+ /// @return Option that contains netmask information
+ static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
+
+ /// @brief Allocation Engine.
+ /// Pointer to the allocation engine that we are currently using
+ /// It must be a pointer, because we will support changing engines
+ /// during normal operation (e.g. to use different allocators)
+ boost::shared_ptr<AllocEngine> alloc_engine_;
+
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/main.cc b/src/bin/dhcp4/main.cc
index bd3be1b..45f1de1 100644
--- a/src/bin/dhcp4/main.cc
+++ b/src/bin/dhcp4/main.cc
@@ -17,6 +17,7 @@
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <boost/lexical_cast.hpp>
@@ -93,9 +94,10 @@ main(int argc, char* argv[]) {
}
// Initialize logging. If verbose, we'll use maximum verbosity.
+ // If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(DHCP4_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
LOG_INFO(dhcp4_logger, DHCP4_STARTING);
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -112,6 +114,10 @@ main(int argc, char* argv[]) {
LOG_ERROR(dhcp4_logger, DHCP4_SESSION_FAIL).arg(ex.what());
// Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
+ // We do need to make sure logging is no longer buffered
+ // since then it would not print until dhcp6 is stopped
+ isc::log::LoggerManager log_manager;
+ log_manager.process();
}
} else {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_STANDALONE);
diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am
index ddc3000..c0ebcb9 100644
--- a/src/bin/dhcp4/tests/Makefile.am
+++ b/src/bin/dhcp4/tests/Makefile.am
@@ -49,9 +49,11 @@ TESTS += dhcp4_unittests
dhcp4_unittests_SOURCES = ../dhcp4_srv.h ../dhcp4_srv.cc ../ctrl_dhcp4_srv.cc
dhcp4_unittests_SOURCES += ../dhcp4_log.h ../dhcp4_log.cc
+dhcp4_unittests_SOURCES += ../config_parser.cc ../config_parser.h
dhcp4_unittests_SOURCES += dhcp4_unittests.cc
dhcp4_unittests_SOURCES += dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
+dhcp4_unittests_SOURCES += config_parser_unittest.cc
nodist_dhcp4_unittests_SOURCES = ../dhcp4_messages.h ../dhcp4_messages.cc
if USE_CLANGPP
@@ -64,12 +66,13 @@ dhcp4_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp4_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp4_unittests_LDADD = $(GTEST_LDADD)
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp4_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
new file mode 100644
index 0000000..ba14edf
--- /dev/null
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -0,0 +1,1293 @@
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <config/ccsession.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp/option4_addrlst.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <boost/foreach.hpp>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <limits.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::data;
+using namespace isc::config;
+
+namespace {
+
+class Dhcp4ParserTest : public ::testing::Test {
+public:
+ Dhcp4ParserTest()
+ :rcode_(-1) {
+ // Open port 0 means to not do anything at all. We don't want to
+ // deal with sockets here, just check if configuration handling
+ // is sane.
+ srv_ = new Dhcpv4Srv(0);
+ }
+
+ // Checks if global parameter of name have expected_value
+ void checkGlobalUint32(string name, uint32_t expected_value) {
+ const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
+ std::map<std::string, uint32_t>::const_iterator it =
+ uint32_defaults.find(name);
+ if (it == uint32_defaults.end()) {
+ ADD_FAILURE() << "Expected uint32 with name " << name
+ << " not found";
+ return;
+ }
+ EXPECT_EQ(expected_value, it->second);
+ }
+
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
+ ~Dhcp4ParserTest() {
+ resetConfiguration();
+ delete srv_;
+ };
+
+ /// @brief Create the simple configuration with single option.
+ ///
+ /// This function allows to set one of the parameters that configure
+ /// option value. These parameters are: "name", "code", "data",
+ /// "csv-format" and "space".
+ ///
+ /// @param param_value string holiding option parameter value to be
+ /// injected into the configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param value.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ std::string createConfigWithOption(const std::string& param_value,
+ const std::string& parameter) {
+ std::map<std::string, std::string> params;
+ if (parameter == "name") {
+ params["name"] = param_value;
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "dhcp-message";
+ params["space"] = param_value;
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "code") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = param_value;
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "data") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
+ }
+ return (createConfigWithOption(params));
+ }
+
+ /// @brief Create simple configuration with single option.
+ ///
+ /// This function creates a configuration for a single option with
+ /// custom values for all parameters that describe the option.
+ ///
+ /// @params params map holding parameters and their values.
+ /// @return configuration string containing custom values of parameters
+ /// describing an option.
+ std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+ std::ostringstream stream;
+ stream << "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {";
+ bool first = true;
+ typedef std::pair<std::string, std::string> ParamPair;
+ BOOST_FOREACH(ParamPair param, params) {
+ if (!first) {
+ stream << ", ";
+ } else {
+ // cppcheck-suppress unreadVariable
+ first = false;
+ }
+ if (param.first == "name") {
+ stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
+ } else if (param.first == "code") {
+ stream << "\"code\": " << param.second << "";
+ } else if (param.first == "data") {
+ stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
+ }
+ }
+ stream <<
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+ return (stream.str());
+ }
+
+ /// @brief Test invalid option parameter value.
+ ///
+ /// This test function constructs the simple configuration
+ /// string and injects invalid option configuration into it.
+ /// It expects that parser will fail with provided option code.
+ ///
+ /// @param param_value string holding invalid option parameter value
+ /// to be injected into configuration string.
+ /// @param parameter name of the parameter to be configured with
+ /// param_value (can be any of "name", "code", "data")
+ void testInvalidOptionParam(const std::string& param_value,
+ const std::string& parameter) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption(param_value, parameter);
+ ElementPtr json = Element::fromJSON(config);
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(1, rcode_);
+ }
+
+ /// @brief Test option against given code and data.
+ ///
+ /// @param option_desc option descriptor that carries the option to
+ /// be tested.
+ /// @param expected_code expected code of the option.
+ /// @param expected_data expected data in the option.
+ /// @param expected_data_len length of the reference data.
+ /// @param extra_data if true extra data is allowed in an option
+ /// after tested data.
+ void testOption(const Subnet::OptionDescriptor& option_desc,
+ uint16_t expected_code, const uint8_t* expected_data,
+ size_t expected_data_len,
+ bool extra_data = false) {
+ // Check if option descriptor contains valid option pointer.
+ ASSERT_TRUE(option_desc.option);
+ // Verify option type.
+ EXPECT_EQ(expected_code, option_desc.option->getType());
+ // We may have many different option types being created. Some of them
+ // have dedicated classes derived from Option class. In such case if
+ // we want to verify the option contents against expected_data we have
+ // to prepare raw buffer with the contents of the option. The easiest
+ // way is to call pack() which will prepare on-wire data.
+ util::OutputBuffer buf(option_desc.option->getData().size());
+ option_desc.option->pack(buf);
+ if (extra_data) {
+ // The length of the buffer must be at least equal to size of the
+ // reference data but it can sometimes be greater than that. This is
+ // because some options carry suboptions that increase the overall
+ // length.
+ ASSERT_GE(buf.getLength() - option_desc.option->getHeaderLen(),
+ expected_data_len);
+ } else {
+ ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
+ expected_data_len);
+ }
+ // Verify that the data is correct. Do not verify suboptions and a header.
+ const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
+ EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+ expected_data_len));
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"valid-lifetime\": 4000, "
+ "\"subnet4\": [ ], "
+ "\"option-def\": [ ], "
+ "\"option-data\": [ ] }";
+
+ try {
+ ElementPtr json = Element::fromJSON(config);
+ status = configureDhcp4Server(*srv_, json);
+ } catch (const std::exception& ex) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. The following configuration was used"
+ << " to reset database: " << std::endl
+ << config << std::endl
+ << " and the following error message was returned:"
+ << ex.what() << std::endl;
+ }
+
+ // status object must not be NULL
+ if (!status) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. Configuration function returned"
+ << " NULL pointer" << std::endl;
+ }
+
+ comment_ = parseAnswer(rcode_, status);
+ // returned value should be 0 (configuration success)
+ if (rcode_ != 0) {
+ FAIL() << "Fatal error: unable to reset configuration database"
+ << " after the test. Configuration function returned"
+ << " error code " << rcode_ << std::endl;
+ }
+ }
+
+ Dhcpv4Srv* srv_;
+
+ int rcode_;
+ ConstElementPtr comment_;
+};
+
+// Goal of this test is a verification if a very simple config update
+// with just a bumped version number. That's the simplest possible
+// config update.
+TEST_F(Dhcp4ParserTest, version) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0}")));
+
+ // returned value must be 0 (configuration accepted)
+ checkResult(x, 0);
+}
+
+/// The goal of this test is to verify that the code accepts only
+/// valid commands and malformed or unsupported parameters are rejected.
+TEST_F(Dhcp4ParserTest, bogusCommand) {
+
+ ConstElementPtr x;
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"bogus\": 5}")));
+
+ // returned value must be 1 (configuration parse error)
+ checkResult(x, 1);
+}
+
+/// The goal of this test is to verify if wrongly defined subnet will
+/// be rejected. Properly defined subnet must include at least one
+/// pool definition.
+TEST_F(Dhcp4ParserTest, emptySubnet) {
+
+ ConstElementPtr status;
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ ], "
+ "\"valid-lifetime\": 4000 }")));
+
+ // returned value should be 0 (success)
+ checkResult(status, 0);
+
+ checkGlobalUint32("rebind-timer", 2000);
+ checkGlobalUint32("renew-timer", 1000);
+ checkGlobalUint32("valid-lifetime", 4000);
+}
+
+/// The goal of this test is to verify if defined subnet uses global
+/// parameter timer definitions.
+TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // check if returned status is OK
+ checkResult(status, 0);
+
+ // Now check if the configuration was indeed handled and we have
+ // expected pool configured.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+}
+
+// This test checks if it is possible to override global values
+// on a per subnet basis.
+TEST_F(Dhcp4ParserTest, subnetLocal) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"renew-timer\": 1, "
+ " \"rebind-timer\": 2, "
+ " \"valid-lifetime\": 4,"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value should be 0 (configuration success)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1, subnet->getT1());
+ EXPECT_EQ(2, subnet->getT2());
+ EXPECT_EQ(4, subnet->getValid());
+}
+
+// Test verifies that a subnet with pool values that do not belong to that
+// pool are rejected.
+TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.4.0/28\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value must be 1 (values error)
+ // as the pool does not belong to that subnet
+ checkResult(status, 1);
+}
+
+// Goal of this test is to verify if pools can be defined
+// using prefix/length notation. There is no separate test for min-max
+// notation as it was tested in several previous tests.
+TEST_F(Dhcp4ParserTest, poolPrefixLen) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.128/28\" ],"
+ " \"subnet\": \"192.0.2.0/24\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+
+ // returned value must be 0 (configuration accepted)
+ checkResult(status, 0);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(1000, subnet->getT1());
+ EXPECT_EQ(2000, subnet->getT2());
+ EXPECT_EQ(4000, subnet->getValid());
+}
+
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp4ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp4ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp4ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp4 option
+/// space) and that it is allowed to define option in the dhcp4
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 109 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp4 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 109,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(109, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp4' option space groups
+ // standard options and the code 100 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+// Goal of this test is to verify that global option
+// data is configured for the subnet if the subnet
+// configuration does not include options configuration.
+TEST_F(Dhcp4ParserTest, optionDataDefaults) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp4' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 56,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp4.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
+ ASSERT_FALSE(desc3.option);
+}
+
+// Goal of this test is to verify options configuration
+// for a single subnet. In particular this test checks
+// that local options configuration overrides global
+// option setting.
+TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0xAB, 0xCD, 0xEF, 0x01, 0x05
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+
+ range = idx.equal_range(23);
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // Do another round of testing with second option.
+ const uint8_t foo2_expected[] = {
+ 0x01
+ };
+ testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Goal of this test is to verify options configuration
+// for multiple subnets.
+TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
+ ConstElementPtr x;
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
+ " } ]"
+ " },"
+ " {"
+ " \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
+ " \"subnet\": \"192.0.3.0/24\", "
+ " \"option-data\": [ {"
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 23,"
+ " \"data\": \"FF\","
+ " \"csv-format\": False"
+ " } ]"
+ " } ],"
+ "\"valid-lifetime\": 4000 }";
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
+ ASSERT_TRUE(subnet1);
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options1->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range1 =
+ idx1.equal_range(56);
+ // Expect single option with the code equal to 56.
+ ASSERT_EQ(1, std::distance(range1.first, range1.second));
+ const uint8_t foo_expected[] = {
+ 0x01, 0x02, 0x03, 0x04, 0x05,
+ 0x06, 0x07, 0x08, 0x09, 0x0A
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range1.first, 56, foo_expected, sizeof(foo_expected));
+
+ // Test another subnet in the same way.
+ Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
+ ASSERT_TRUE(subnet2);
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options2->size());
+
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range2 =
+ idx2.equal_range(23);
+ ASSERT_EQ(1, std::distance(range2.first, range2.second));
+
+ const uint8_t foo2_expected[] = { 0xFF };
+ testOption(*range2.first, 23, foo2_expected, sizeof(foo2_expected));
+}
+
+// Verify that empty option name is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameEmpty) {
+ // Empty option names not allowed.
+ testInvalidOptionParam("", "name");
+}
+
+// Verify that empty option name with spaces is rejected
+// in the configuration.
+TEST_F(Dhcp4ParserTest, optionNameSpaces) {
+ // Spaces in option names not allowed.
+ testInvalidOptionParam("option foo", "name");
+}
+
+// Verify that negative option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNegative) {
+ // Check negative option code -4. This should fail too.
+ testInvalidOptionParam("-4", "code");
+}
+
+// Verify that out of bounds option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeNonUint8) {
+ // The valid option codes are uint16_t values so passing
+ // uint16_t maximum value incremented by 1 should result
+ // in failure.
+ testInvalidOptionParam("257", "code");
+}
+
+// Verify that zero option code is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionCodeZero) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("0", "code");
+}
+
+// Verify that option data which contains non hexadecimal characters
+// is rejected by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataInvalidChar) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("01020R", "data");
+}
+
+// Verify that option data containins '0x' prefix is rejected
+// by the configuration.
+TEST_F(Dhcp4ParserTest, optionDataUnexpectedPrefix) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("0x0102", "data");
+}
+
+// Verify that option data consisting od an odd number of
+// hexadecimal digits is rejected in the configuration.
+TEST_F(Dhcp4ParserTest, optionDataOddLength) {
+ // Option code 0 is reserved and should not be accepted
+ // by configuration parser.
+ testInvalidOptionParam("123", "data");
+}
+
+// Verify that either lower or upper case characters are allowed
+// to specify the option data.
+TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
+ ConstElementPtr x;
+ std::string config = createConfigWithOption("0a0b0C0D", "data");
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(56);
+ // Expect single option with the code equal to 100.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ const uint8_t foo_expected[] = {
+ 0x0A, 0x0B, 0x0C, 0x0D
+ };
+ // Check if option is valid in terms of code and carried data.
+ testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
+}
+
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp4ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "nis-servers";
+ params["space"] = "dhcp4";
+ // Option code 41 means nis-servers.
+ params["code"] = "41";
+ // Specify option values in a CSV (user friendly) format.
+ params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+ params["csv-format"] = "True";
+
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options =
+ subnet->getOptionDescriptors("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(DHO_NIS_SERVERS);
+ // Expect single option with the code equal to NIS_SERVERS option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option4AddrLst> option_addrs =
+ boost::dynamic_pointer_cast<Option4AddrLst>(option);
+ // If cast is unsuccessful than option returned was of a
+ // differnt type than Option6IA. This is wrong.
+ ASSERT_TRUE(option_addrs);
+
+ // Get addresses from the option.
+ Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+ // Verify that the addresses have been configured correctly.
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("192.0.2.10", addrs[0].toText());
+ EXPECT_EQ("192.0.2.1", addrs[1].toText());
+ EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
+/// This test checks if Uint32Parser can really parse the whole range
+/// and properly err of out of range values. As we can't call Uint32Parser
+/// directly, we are exploiting the fact that it is used to parse global
+/// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
+TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
+
+ ConstElementPtr status;
+
+ // CASE 1: 0 - minimum value, should work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 0}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ checkGlobalUint32("renew-timer", 0);
+
+ // CASE 2: 4294967295U (UINT_MAX) should work as well
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 4294967295}")));
+
+ // returned value must be ok (0 is a proper value)
+ checkResult(status, 0);
+ checkGlobalUint32("renew-timer", 4294967295U);
+
+ // CASE 3: 4294967296U (UINT_MAX + 1) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": 4294967296}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+
+ // CASE 4: -1 (UINT_MIN -1 ) should not work
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_,
+ Element::fromJSON("{\"version\": 0,"
+ "\"renew-timer\": -1}")));
+
+ // returned value must be rejected (1 configuration error)
+ checkResult(status, 1);
+}
+
+};
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index ac6f0bb..bc2246f 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,7 +19,11 @@
#include <dhcp/dhcp4.h>
#include <dhcp/option.h>
#include <dhcp4/dhcp4_srv.h>
-
+#include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
#include <fstream>
@@ -33,43 +37,41 @@ using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv4Srv: public Dhcpv4Srv {
- // "naked" DHCPv4 server, exposes internal fields
+ // "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
-
- boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
- return Dhcpv4Srv::processDiscover(discover);
- }
- boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request) {
- return Dhcpv4Srv::processRequest(request);
- }
- void processRelease(boost::shared_ptr<Pkt4>& release) {
- return Dhcpv4Srv::processRelease(release);
- }
- void processDecline(boost::shared_ptr<Pkt4>& decline) {
- Dhcpv4Srv::processDecline(decline);
- }
- boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform) {
- return Dhcpv4Srv::processInform(inform);
- }
+ NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+ using Dhcpv4Srv::processDiscover;
+ using Dhcpv4Srv::processRequest;
+ using Dhcpv4Srv::processRelease;
+ using Dhcpv4Srv::processDecline;
+ using Dhcpv4Srv::processInform;
+ using Dhcpv4Srv::getServerID;
+ using Dhcpv4Srv::sanityCheck;
};
class Dhcpv4SrvTest : public ::testing::Test {
public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes common objects used in many tests.
+ /// Also sets up initial configuration in CfgMgr.
Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
- fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
- if (if_nametoindex("lo") > 0) {
- fakeifaces << "lo 127.0.0.1";
- } else if (if_nametoindex("lo0") > 0) {
- fakeifaces << "lo0 127.0.0.1";
- }
- fakeifaces.close();
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
+ 2000, 3000));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().addSubnet4(subnet_);
}
+ /// @brief checks that the response matches request
+ /// @param q query (client's message)
+ /// @param a answer (server's message)
void MessageCheck(const boost::shared_ptr<Pkt4>& q,
const boost::shared_ptr<Pkt4>& a) {
ASSERT_TRUE(q);
@@ -80,7 +82,7 @@ public:
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
- // check that bare minimum of required options are there
+ // Check that bare minimum of required options are there
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
@@ -90,29 +92,208 @@ public:
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
- // check that something is offered
+ // Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
}
+ /// @brief generates client-id option
+ ///
+ /// Generate client-id option of specified length
+ /// Ids with different lengths are sufficent to generate
+ /// unique ids. If more fine grained control is required,
+ /// tests generate client-ids on their own.
+ /// Sets client_id_ field.
+ /// @param size size of the client-id to be generated
+ OptionPtr generateClientId(size_t size = 4) {
+
+ OptionBuffer clnt_id(size);
+ for (int i = 0; i < size; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ client_id_ = ClientIdPtr(new ClientId(clnt_id));
+
+ return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(),
+ clnt_id.begin() + size)));
+ }
+
+ /// @brief generate hardware address
+ ///
+ /// @param size size of the generated MAC address
+ /// @param pointer to Hardware Address object
+ HWAddrPtr generateHWAddr(size_t size = 6) {
+ const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
+ OptionBuffer mac(size);
+ for (int i = 0; i < size; ++i) {
+ mac[i] = 50 + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+ }
+
+ /// Check that address was returned from proper range, that its lease
+ /// lifetime is correct, that T1 and T2 are returned properly
+ /// @param rsp response to be checked
+ /// @param subnet subnet that should be used to verify assigned address and options
+ /// @param t1_mandatory is T1 mandatory?
+ /// @param t2_mandatory is T2 mandatory?
+ void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
+ bool t1_mandatory = false, bool t2_mandatory = false) {
+
+ // Technically inPool implies inRange, but let's be on the safe
+ // side and check both.
+ EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
+ EXPECT_TRUE(subnet->inPool(rsp->getYiaddr()));
+
+ // Check lease time
+ OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
+ if (!opt) {
+ ADD_FAILURE() << "Lease time option missing in response";
+ } else {
+ EXPECT_EQ(opt->getUint32(), subnet->getValid());
+ }
+
+ // Check T1 timer
+ opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT1());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T1 option missing";
+ }
+ }
+
+ // Check T2 timer
+ opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT2());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T2 option missing";
+ }
+ }
+ }
+
+ /// @brief Basic checks for generated response (message type and trans-id).
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_message_type expected message type
+ /// @param expected_transid expected transaction-id
+ void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ /// @brief Checks if the lease sent to client is present in the database
+ ///
+ /// @param rsp response packet to be validated
+ /// @param client_id expected client-identifier (or NULL)
+ /// @param HWAddr expected hardware address (not used now)
+ /// @param expected_addr expected address
+ Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+ const HWAddrPtr&, const IOAddress& expected_addr) {
+
+ ClientIdPtr id;
+ if (client_id) {
+ OptionBuffer data = client_id->getData();
+ id.reset(new ClientId(data));
+ }
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
+ if (!lease) {
+ cout << "Lease for " << expected_addr.toText()
+ << " not found in the database backend.";
+ return (Lease4Ptr());
+ }
+
+ EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+
+ EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+ if (client_id) {
+ EXPECT_TRUE(*lease->client_id_ == *id);
+ }
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
+ /// @param rsp response packet to be validated
+ /// @param expected_srvid expected value of server-id
+ void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
+ // Check that server included its server-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getType(), expected_srvid->getType() );
+ EXPECT_EQ(opt->len(), expected_srvid->len() );
+ EXPECT_TRUE(opt->getData() == expected_srvid->getData());
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+ /// @param rsp response packet to be validated
+ /// @param expected_clientid expected value of client-id
+ void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(expected_clientid->getType(), opt->getType());
+ EXPECT_EQ(expected_clientid->len(), opt->len());
+ EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+ }
+
~Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
+ CfgMgr::instance().deleteSubnets4();
};
+
+ /// @brief A subnet used in most tests
+ Subnet4Ptr subnet_;
+
+ /// @brief A pool used in most tests
+ Pool4Ptr pool_;
+
+ /// @brief A client-id used in most tests
+ ClientIdPtr client_id_;
};
+// Sanity check. Verifies that both Dhcpv4Srv and its derived
+// class NakedDhcpv4Srv can be instantiated and destroyed.
TEST_F(Dhcpv4SrvTest, basic) {
- // nothing to test. DHCPv4_srv instance is created
- // in test fixture. It is destroyed in destructor
+ // Check that the base class can be instantiated
Dhcpv4Srv* srv = NULL;
ASSERT_NO_THROW({
srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
});
-
delete srv;
+
+ // Check that the derived class can be instantiated
+ NakedDhcpv4Srv* naked_srv = NULL;
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+ delete naked_srv;
+
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(0);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+
+ delete naked_srv;
}
+// Verifies that received DISCOVER can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processDiscover) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = 255 - i;
@@ -127,26 +308,26 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
pkt->setRemoteAddr(IOAddress("192.0.2.56"));
pkt->setGiaddr(IOAddress("192.0.2.67"));
- // let's make it a relayed message
+ // Let's make it a relayed message
pkt->setHops(3);
pkt->setRemotePort(DHCP4_SERVER_PORT);
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
MessageCheck(pkt, offer);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
pkt->setHops(0);
pkt->setGiaddr(IOAddress("0.0.0.0"));
pkt->setRemotePort(DHCP4_CLIENT_PORT);
@@ -155,12 +336,12 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
@@ -169,8 +350,15 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
delete srv;
}
+// Verifies that received REQUEST can be processed correctly,
+// that the ACK message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See RequestBasic.
TEST_F(Dhcpv4SrvTest, processRequest) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
@@ -185,22 +373,22 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
req->setRemoteAddr(IOAddress("192.0.2.56"));
req->setGiaddr(IOAddress("192.0.2.67"));
- // should not throw
+ // Should not throw
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
MessageCheck(req, ack);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
req->setHops(0);
req->setGiaddr(IOAddress("0.0.0.0"));
req->setRemotePort(DHCP4_CLIENT_PORT);
@@ -209,12 +397,12 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
ack = srv->processDiscover(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPOFFER, ack->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
@@ -228,14 +416,11 @@ TEST_F(Dhcpv4SrvTest, processRelease) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processRelease(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
-
delete srv;
}
@@ -244,13 +429,11 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processDecline(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
delete srv;
}
@@ -259,15 +442,15 @@ TEST_F(Dhcpv4SrvTest, processInform) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processInform(pkt);
);
- // should return something
+ // Should return something
EXPECT_TRUE(srv->processInform(pkt));
- // TODO: Implement more reasonable tests before starting
+ // @todo Implement more reasonable tests before starting
// work on processSomething() method.
delete srv;
@@ -305,4 +488,642 @@ TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
}
}
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint, but that hint is invalid
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address (!= hint)
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("10.1.2.3");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.107"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
+// This test checks that the server is offering different addresses to different
+// clients in OFFERs. Please note that OFFER is not a guarantee that such
+// an address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same offer as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. OFFER is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis1 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr dis2 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 2345));
+ Pkt4Ptr dis3 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 3456));
+
+ dis1->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis2->setRemoteAddr(IOAddress("192.0.2.2"));
+ dis3->setRemoteAddr(IOAddress("192.0.2.3"));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ dis1->addOption(clientid1);
+ dis2->addOption(clientid2);
+ dis3->addOption(clientid3);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer1 = srv->processDiscover(dis1);
+ Pkt4Ptr offer2 = srv->processDiscover(dis2);
+ Pkt4Ptr offer3 = srv->processDiscover(dis3);
+
+ // Check if we get response at all
+ checkResponse(offer1, DHCPOFFER, 1234);
+ checkResponse(offer2, DHCPOFFER, 2345);
+ checkResponse(offer3, DHCPOFFER, 3456);
+
+ IOAddress addr1 = offer1->getYiaddr();
+ IOAddress addr2 = offer2->getYiaddr();
+ IOAddress addr3 = offer3->getYiaddr();
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(offer1, subnet_);
+ checkAddressParams(offer2, subnet_);
+ checkAddressParams(offer3, subnet_);
+
+ // Check DUIDs
+ checkServerId(offer1, srv->getServerID());
+ checkServerId(offer2, srv->getServerID());
+ checkServerId(offer3, srv->getServerID());
+ checkClientId(offer1, clientid1);
+ checkClientId(offer2, clientid2);
+ checkClientId(offer3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed a single REQUEST message with:
+// - client-id option
+// - hwaddr information
+// - requested address (that the client received in DISCOVER/OFFER exchange)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address
+//
+// Test verifies that the lease is actually in the database.
+TEST_F(Dhcpv4SrvTest, RequestBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ IOAddress hint("192.0.2.107");
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+ req->setYiaddr(hint);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = checkLease(ack, clientid, req->getHWAddr(), hint);
+
+ ASSERT_TRUE(l);
+ LeaseMgrFactory::instance().deleteLease(l->addr_);
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed 3 REQUEST messages with:
+// - client-id option (differs between messages)
+// - hwaddr information (differs between messages)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address (different for each client)
+TEST_F(Dhcpv4SrvTest, ManyRequests) {
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress req_addr1("192.0.2.105");
+ const IOAddress req_addr2("192.0.2.101");
+ const IOAddress req_addr3("192.0.2.109");
+ const IOAddress relay("192.0.2.1");
+
+ Pkt4Ptr req1 = Pkt4Ptr(new Pkt4(DHCPOFFER, 1234));
+ Pkt4Ptr req2 = Pkt4Ptr(new Pkt4(DHCPOFFER, 2345));
+ Pkt4Ptr req3 = Pkt4Ptr(new Pkt4(DHCPOFFER, 3456));
+
+ req1->setRemoteAddr(relay);
+ req2->setRemoteAddr(relay);
+ req3->setRemoteAddr(relay);
+
+ req1->setYiaddr(req_addr1);
+ req2->setYiaddr(req_addr2);
+ req3->setYiaddr(req_addr3);
+
+ req1->setHWAddr(generateHWAddr(6));
+ req2->setHWAddr(generateHWAddr(7));
+ req3->setHWAddr(generateHWAddr(8));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack1 = srv->processRequest(req1);
+ Pkt4Ptr ack2 = srv->processRequest(req2);
+ Pkt4Ptr ack3 = srv->processRequest(req3);
+
+ // Check if we get response at all
+ checkResponse(ack1, DHCPACK, 1234);
+ checkResponse(ack2, DHCPACK, 2345);
+ checkResponse(ack3, DHCPACK, 3456);
+
+ IOAddress addr1 = ack1->getYiaddr();
+ IOAddress addr2 = ack2->getYiaddr();
+ IOAddress addr3 = ack3->getYiaddr();
+
+ // Check that every client received the address it requested
+ EXPECT_EQ(req_addr1.toText(), addr1.toText());
+ EXPECT_EQ(req_addr2.toText(), addr2.toText());
+ EXPECT_EQ(req_addr3.toText(), addr3.toText());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(ack1, subnet_);
+ checkAddressParams(ack2, subnet_);
+ checkAddressParams(ack3, subnet_);
+
+ // Check DUIDs
+ checkServerId(ack1, srv->getServerID());
+ checkServerId(ack2, srv->getServerID());
+ checkServerId(ack3, srv->getServerID());
+ checkClientId(ack1, clientid1);
+ checkClientId(ack2, clientid2);
+ checkClientId(ack3, clientid3);
+
+ // Check that leases are in the database
+ Lease4Ptr l = checkLease(ack1, clientid1, req1->getHWAddr(), addr1);
+ EXPECT_TRUE(l);
+ l = checkLease(ack2, clientid2, req2->getHWAddr(), addr2);
+ l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+
+// This test verifies that incoming (positive) REQUEST/Renewing can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ // EXPECT_EQ(l->t1_, temp_t1);
+ // EXPECT_EQ(l->t2_, temp_t2);
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+
+ req->addOption(clientid);
+ req->addOption(srv->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// @todo: Implement tests for rejecting renewals
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
+
+ // Empty packet, no server-id
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
+
+ pkt->addOption(srv->getServerID());
+
+ // Server-id is mandatory and present = no exception
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
+
+ // Server-id is forbidden, but present => exception
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
+ RFCViolation);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly.
+// As there is no REPLY in DHCPv4, the only thing to verify here is that
+// the lease is indeed removed from the database.
+TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // The lease should be gone from LeaseMgr
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 0);
+
+ // Try to get it by hw/subnet_id combination
+ l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 0);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Ok, the lease is *really* not there.
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+TEST_F(Dhcpv4SrvTest, ReleaseReject) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t t1 = 50;
+ const uint32_t t2 = 75;
+ const uint32_t valid = 100;
+ const time_t timestamp = time(NULL) - 10;
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t bogus_mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr bogus_hw(new HWAddr(bogus_mac_addr, sizeof(bogus_mac_addr), HTYPE_ETHER));
+ OptionPtr bogus_clientid = generateClientId(7); // different length
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(bogus_hw);
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: Lease is not known to the server");
+
+ // There is nothing to check here. The lease is not there and server does
+ // not send anything back. This case is enumerated here just for keeping
+ // parity with similar test in DHCPv6.
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // CASE 2: Lease is known and belongs to this client, but to a different hardware
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but uses different HW addr");
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ valid, t1, t2, timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ rel->setHWAddr(bogus_hw);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease was not removed (due to hardware address mis-match)
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ rel->setHWAddr(hw); // proper HW address this time
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(bogus_clientid); // but invalid client-id
+
+ OptionPtr x = rel->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Final sanity check. Verify that with valid hw and client-id release is successful
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(clientid);
+
+ // It should work this time
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_test.py b/src/bin/dhcp4/tests/dhcp4_test.py
index 444dfcf..e493e04 100644
--- a/src/bin/dhcp4/tests/dhcp4_test.py
+++ b/src/bin/dhcp4/tests/dhcp4_test.py
@@ -45,11 +45,30 @@ class TestDhcpv4Daemon(unittest.TestCase):
def tearDown(self):
pass
+ def readPipe(self, pipe_fd):
+ """
+ Reads bytes from a pipe and returns a character string. If nothing is
+ read, or if there is an error, an empty string is returned.
+
+ pipe_fd - Pipe file descriptor to read
+ """
+ try:
+ data = os.read(pipe_fd, 16384)
+ # Make sure we have a string
+ if (data is None):
+ data = ""
+ else:
+ data = str(data)
+ except OSError:
+ data = ""
+
+ return data
+
def runCommand(self, params, wait=1):
"""
- This method runs dhcp4 and returns a tuple: (returncode, stdout, stderr)
+ This method runs a command and returns a tuple: (returncode, stdout, stderr)
"""
- ## @todo: Convert this into generic method and reuse it in dhcp6
+ ## @todo: Convert this into generic method and reuse it in dhcp4 and dhcp6
print("Running command: %s" % (" ".join(params)))
@@ -89,46 +108,48 @@ class TestDhcpv4Daemon(unittest.TestCase):
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
- # There's potential problem if b10-dhcp4 prints out more
- # than 16kB of text
- try:
- output = os.read(self.stdout_pipes[0], 16384)
- except OSError:
- print("No data available from stdout")
- output = ""
-
- # read can return None. Make sure we have a string
- if (output is None):
- output = ""
-
- try:
- error = os.read(self.stderr_pipes[0], 16384)
- except OSError:
- print("No data available on stderr")
- error = ""
-
- # read can return None. Make sure we have a string
- if (error is None):
- error = ""
-
-
- try:
- if (not pi.process.poll()):
- # let's be nice at first...
+ # As we don't know how long the subprocess will take to start and
+ # produce output, we'll loop and sleep for 250 ms between each
+ # iteration. To avoid an infinite loop, we'll loop for a maximum
+ # of five seconds: that should be enough.
+ for count in range(20):
+ # Read something from stderr and stdout (these reads don't block).
+ output = self.readPipe(self.stdout_pipes[0])
+ error = self.readPipe(self.stderr_pipes[0])
+
+ # If the process has already exited, or if it has output something,
+ # quit the loop now.
+ if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
+ break
+
+ # Process still running, try again in 250 ms.
+ time.sleep(0.25)
+
+ # Exited loop, kill the process if it is still running
+ if pi.process.poll() is None:
+ try:
pi.process.terminate()
- except OSError:
- print("Ignoring failed kill attempt. Process is dead already.")
+ except OSError:
+ print("Ignoring failed kill attempt. Process is dead already.")
# call this to get returncode, process should be dead by now
rc = pi.process.wait()
# Clean up our stdout/stderr munging.
os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.close(self.stdout_old)
os.close(self.stdout_pipes[0])
os.dup2(self.stderr_old, sys.stderr.fileno())
+ os.close(self.stderr_old)
os.close(self.stderr_pipes[0])
+ # Free up resources (file descriptors) from the ProcessInfo object
+ # TODO: For some reason, this gives an error if the process has ended,
+ # although it does cause all descriptors still allocated to the
+ # object to be freed.
+ pi = None
+
print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
% (rc, len(output), len(error)) )
diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc
index 0ed61b7..93e836b 100644
--- a/src/bin/dhcp4/tests/dhcp4_unittests.cc
+++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc
@@ -13,13 +13,16 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <log/logger_support.h>
-
+#include <dhcp4/dhcp4_log.h>
#include <gtest/gtest.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the B10_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
isc::log::initLogger();
int result = RUN_ALL_TESTS();
diff --git a/src/bin/dhcp6/Makefile.am b/src/bin/dhcp6/Makefile.am
index 1d9766f..decd986 100644
--- a/src/bin/dhcp6/Makefile.am
+++ b/src/bin/dhcp6/Makefile.am
@@ -59,14 +59,14 @@ if USE_CLANGPP
b10_dhcp6_CXXFLAGS = -Wno-unused-parameter
endif
-b10_dhcp6_LDADD = $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
b10_dhcp6_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-b10_dhcp6_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+b10_dhcp6_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
b10_dhcp6dir = $(pkgdatadir)
b10_dhcp6_DATA = dhcp6.spec
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index d6f3afe..f2eb34c 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,12 +18,15 @@
#include <dhcp/libdhcp++.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
+#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
#include <log/logger_support.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
@@ -38,37 +41,56 @@
#include <stdint.h>
using namespace std;
+using namespace isc;
using namespace isc::data;
+using namespace isc::dhcp;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
-/// @brief an auxiliary type used for storing an element name and its parser
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
-/// @brief a factory method that will create a parser for a given element name
-typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+/// @brief Factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-/// @brief a collection of factories that create parsers for specified element names
+/// @brief Collection of factories that create parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
typedef std::map<string, uint32_t> Uint32Storage;
-/// @brief a collection of elements that store string values
+/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
-/// @brief a collection of pools
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
-typedef std::vector<Pool6Ptr> PoolStorage;
+typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -79,6 +101,10 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+
/// @brief a dummy configuration parser
///
/// This is a debugging parser. It does not configure anything,
@@ -90,7 +116,7 @@ public:
/// @brief Constructor
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -99,7 +125,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -108,12 +134,12 @@ public:
value_ = new_config;
}
- /// @brief pretends to apply the configuration
+ /// @brief Pretends to apply the configuration.
///
/// This is a method required by the base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -125,11 +151,11 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
-protected:
+private:
/// name of the parsed parameter
std::string param_name_;
@@ -137,6 +163,86 @@ protected:
ConstElementPtr value_;
};
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -144,10 +250,10 @@ protected:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6-config-inherit page.
+/// @ref dhcpv6ConfigInherit page.
///
/// @todo this class should be turned into the template class which
/// will handle all uintX_types of data (see ticket #2415).
@@ -155,18 +261,29 @@ class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
+ ///
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
+ /// @throw isc::dhcp::DhcpConfigError if failed to parse
+ /// the configuration parameter as uint32_t value.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
bool parse_error = false;
// Cast the provided value to int64 value to check.
int64_t int64value = 0;
@@ -180,62 +297,54 @@ public:
parse_error = true;
}
if (!parse_error) {
+ // Check that the value is not out of bounds.
if ((int64value < 0) ||
(int64value > std::numeric_limits<uint32_t>::max())) {
parse_error = true;
} else {
- try {
- value_ = boost::lexical_cast<uint32_t>(value->str());
- } catch (const boost::bad_lexical_cast &) {
- parse_error = true;
- }
+ // A value is not out of bounds so let's cast it to
+ // the uint32_t type.
+ value_ = static_cast<uint32_t>(int64value);
}
}
-
+ // Invalid value provided.
if (parse_error) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
+ isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
<< " as unsigned 32-bit integer.");
}
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet6Parser when constructing
- /// the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed uint32_t value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs Uint32Parser objects
+ /// @brief Factory that constructs Uint32Parser objects.
///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ /// @param param_name name of the parameter to be parsed.
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
- /// @param storage pointer to the storage container
+ /// @param storage pointer to the storage container.
void setStorage(Uint32Storage* storage) {
storage_ = storage;
}
-protected:
+private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
-
/// name of the parameter to be parsed
std::string param_name_;
-
/// the actual parsed value
uint32_t value_;
};
@@ -247,71 +356,75 @@ protected:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using the
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6-config-inherit page.
+/// @ref dhcpv6ConfigInherit page.
class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
+ ///
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
+ : storage_(&string_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
- /// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// Parses configuration parameter's value as string.
///
/// @param value pointer to the content of parsed values
+ /// @throws DhcpConfigError if the parsed parameter's name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
value_ = value->str();
boost::erase_all(value_, "\"");
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs StringParser objects
+ /// @brief Factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(StringStorage* storage) {
storage_ = storage;
}
-protected:
- /// pointer to the storage, where parsed value will be stored
+private:
+ /// Pointer to the storage, where parsed value will be stored
StringStorage* storage_;
-
- /// name of the parameter to be parsed
+ /// Name of the parameter to be parsed
std::string param_name_;
-
- /// the actual parsed value
+ /// The actual parsed value
std::string value_;
};
-
/// @brief parser for interface list definition
///
/// This parser handles Dhcp6/interface entry.
@@ -329,9 +442,10 @@ public:
/// "interface" parameter only. All other types will throw exception.
///
/// @param param_name name of the configuration parameter being parsed
+ /// @throw BadValue if supplied parameter name is not "interface"
InterfaceListConfigParser(const std::string& param_name) {
if (param_name != "interface") {
- isc_throw(NotImplemented, "Internal error. Interface configuration "
+ isc_throw(isc::BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -339,7 +453,7 @@ public:
/// @brief parses parameters value
///
/// Parses configuration entry (list of parameters) and stores it in
- /// storage. See \ref setStorage() for details.
+ /// storage.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
@@ -357,11 +471,11 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
-protected:
+private:
/// contains list of network interfaces
vector<string> interfaces_;
};
@@ -381,7 +495,7 @@ public:
/// @brief constructor.
PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
+ : pools_(NULL) {
// ignore parameter name, it is always Dhcp6/subnet6[X]/pool
}
@@ -390,10 +504,14 @@ public:
/// This method parses the actual list of interfaces.
/// No validation is done at this stage, everything is interpreted as
/// interface name.
+ /// @param pools_list list of pools defined for a subnet
+ /// @throw isc::InvalidOperation if storage was not specified
+ /// (setStorage() not called)
void build(ConstElementPtr pools_list) {
+
// setStorage() should have been called before build
if (!pools_) {
- isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+ isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
" but pool parser asked to parse pools");
}
@@ -429,12 +547,12 @@ public:
// be checked in Pool6 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -442,16 +560,16 @@ public:
pos = txt.find("-");
if (pos != string::npos) {
// using min-max notation
- IOAddress min(txt.substr(0,pos - 1));
+ IOAddress min(txt.substr(0, pos));
IOAddress max(txt.substr(pos + 1));
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -459,48 +577,62 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6-config-inherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(PoolStorage* storage) {
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
-protected:
+private:
/// @brief pointer to the actual Pools storage
///
/// This is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
+
/// @brief Parser for option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful than an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere util support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
class OptionDataParser : public DhcpConfigParser {
public:
@@ -524,45 +656,53 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp6ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError if invalid parameter specified in
/// the configuration.
/// @throw isc::InvalidOperation if failed to set storage prior to
/// calling build.
- /// @throw isc::BadValue if option data storage is invalid.
virtual void build(ConstElementPtr option_data_entries) {
+
if (options_ == NULL) {
isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
"parsing option data.");
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
- } else if (param.first == "data") {
- boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
if (value_parser) {
- value_parser->setStorage(&string_values_);
+ value_parser->setStorage(&boolean_values_);
parser = value_parser;
}
} else {
- isc_throw(Dhcp6ConfigError,
- "Parser error: option-data parameter not supported: "
+ isc_throw(DhcpConfigError,
+ "parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -578,16 +718,21 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
"commiting option data.");
} else if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is not
// than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
@@ -597,7 +742,7 @@ public:
idx.erase(range.first, range.second);
}
// Append new option to the main storage.
- options_->push_back(option_descriptor_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -625,63 +770,103 @@ private:
/// is intitialized but this check is not needed here because it is done
/// in the \ref build function.
///
- /// @throw Dhcp6ConfigError if parameters provided in the configuration
+ /// @throw DhcpConfigError if parameters provided in the configuration
/// are invalid.
void createOption() {
+
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
// does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getUint32Param("code");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv6.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " exceed " << std::numeric_limits<uint16_t>::max());
}
// Check that the option name has been specified, is non-empty and does not
// contain spaces.
// @todo possibly some more restrictions apply here?
- std::string option_name = getStringParam("name");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
<< " empty");
} else if (option_name.find(" ") != std::string::npos) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V6, option_code);
+
+ } else if (option_space == "dhcp4") {
+ isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
+ << " for DHCPv4 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
- // Get all existing DHCPv6 option definitions. The one that matches
- // our option will be picked and used to create it.
- OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
- // Get search index #1. It allows searching for options definitions
- // using option type value.
- const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
- // Get all option definitions matching option code we want to create.
- const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
- size_t num_defs = std::distance(range.first, range.second);
+
OptionPtr option;
- // Currently we do not allow duplicated definitions and if there are
- // any duplicates we issue internal server error.
- if (num_defs > 1) {
- isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
- << " supported to initialize multiple option definitions"
- << " for the same option code. This will be supported once"
- << " there option spaces are implemented.");
- } else if (num_defs == 0) {
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -695,60 +880,52 @@ private:
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} else {
- // We have exactly one option definition for the particular option code
- // use it to create the option instance.
- const OptionDefinitionPtr& def = *(range.first);
- // getFactory should never return NULL pointer.
- Option::Factory* factory = def->getFactory();
- assert(factory != NULL);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
- OptionPtr option = factory(Option::V6, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V6, option_code, data_tokens) :
+ def->optionFactory(Option::V6, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
- << " option definition (code " << option_code << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
<< ex.what());
}
}
- }
-
- /// @brief Get a parameter from the strings storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- std::string getStringParam(const std::string& param_id) const {
- StringStorage::const_iterator param = string_values_.find(param_id);
- if (param == string_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
- }
-
- /// @brief Get a parameter from the uint32 values storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- uint32_t getUint32Param(const std::string& param_id) const {
- Uint32Storage::const_iterator param = uint32_values_.find(param_id);
- if (param == uint32_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
/// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
+ isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -774,7 +951,7 @@ public:
/// for options within a single subnet and creates options' instances.
///
/// @param option_data_list pointer to a list of options' data sets.
- /// @throw Dhcp6ConfigError if option parsing failed.
+ /// @throw DhcpConfigError if option parsing failed.
void build(ConstElementPtr option_data_list) {
BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -814,7 +991,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -829,6 +1006,254 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in the data store.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
@@ -845,6 +1270,8 @@ public:
/// @brief parses parameter value
///
/// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -856,65 +1283,133 @@ public:
// a setStorage and build methods are invoked.
// Try uint32 type parser.
- if (buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
- param.second)) {
- // Storage set, build invoked on the parser, proceed with
- // next configuration element.
- continue;
- }
- // Try string type parser.
- if (buildParser<StringParser, StringStorage >(parser, string_values_,
- param.second)) {
- continue;
- }
- // Try pools parser.
- if (buildParser<PoolParser, PoolStorage >(parser, pools_,
- param.second)) {
- continue;
- }
- // Try option data parser.
- if (buildParser<OptionDataListParser, OptionStorage >(parser, options_,
- param.second)) {
- continue;
+ if (!buildParser<Uint32Parser, Uint32Storage >(parser, uint32_values_,
+ param.second) &&
+ // Try string type parser.
+ !buildParser<StringParser, StringStorage >(parser, string_values_,
+ param.second) &&
+ // Try pool parser.
+ !buildParser<PoolParser, PoolStorage >(parser, pools_,
+ param.second) &&
+ // Try option data parser.
+ !buildParser<OptionDataListParser, OptionStorage >(parser, options_,
+ param.second)) {
+ // Appropriate parsers are created in the createSubnet6ConfigParser
+ // and they should be limited to those that we check here for. Thus,
+ // if we fail to find a matching parser here it is a programming error.
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
- }
- /// @brief commits received configuration.
- ///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet6
- /// objects. Subnet6 are then added to DHCP CfgMgr.
- void commit() {
- // Invoke commit on all sub-data parsers.
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
+ // Create a subnet.
+ createSubnet();
+ }
+
+ /// @brief Adds the created subnet to a server's configuration.
+ void commit() {
+ if (subnet_) {
+ isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
+
+ // Find a subnet string.
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
-
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> pref = getParam("preferred-lifetime");
Triplet<uint32_t> valid = getParam("valid-lifetime");
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+
+ string iface;
+ StringStorage::const_iterator iface_iter = string_values_.find("interface");
+ if (iface_iter != string_values_.end()) {
+ iface = iface_iter->second;
+ }
+
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
@@ -923,126 +1418,113 @@ public:
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Create a new subnet.
+ subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Add pools to it.
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool6(*it);
+ subnet_->addPool(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ // Configure interface, if defined
+ if (!iface.empty()) {
+ if (!IfaceMgr::instance().getIface(iface)) {
+ isc_throw(DhcpConfigError, "Specified interface name " << iface
+ << " for subnet " << subnet_->toText() << " is not present"
+ << " in the system.");
+ }
+
+ subnet_->setIface(iface);
+ }
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
-
- CfgMgr::instance().addSubnet6(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
/// @param config_id name od the entry
+ ///
/// @return parser object for specified entry name
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "subnet", StringParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "pool", PoolParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["interface"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
// Used for debugging only.
// return new DebugParser(config_id);
- isc_throw(NotImplemented,
- "Parser error: Subnet6 parameter not supported: "
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: subnet6 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1050,7 +1532,8 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- Triplet<uint32_t> getParam(const std::string& name) {
+ /// @throw DhcpConfigError when requested parameter is not present
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
Uint32Storage::iterator global = uint32_defaults.find(name);
@@ -1066,9 +1549,9 @@ private:
}
if (found) {
- return (Triplet<uint32_t>(value));
+ return (isc::dhcp::Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+ isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1088,6 +1571,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::Subnet6Ptr subnet_;
};
/// @brief this class parses a list of subnets
@@ -1133,7 +1619,7 @@ public:
// the old one and replace with the new one.
// remove old subnets
- CfgMgr::instance().deleteSubnets6();
+ isc::dhcp::CfgMgr::instance().deleteSubnets6();
BOOST_FOREACH(ParserPtr subnet, subnets_) {
subnet->commit();
@@ -1144,7 +1630,7 @@ public:
/// @brief Returns Subnet6ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets6ListConfigParser object
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets6ListConfigParser(param_name));
}
@@ -1152,6 +1638,11 @@ public:
ParserCollection subnets_;
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1159,28 +1650,19 @@ public:
///
/// @param config_id pointer to received global configuration entry
/// @return parser for specified global DHCPv6 parameter
+/// @throw NotImplemented if trying to create a parser for unknown config element
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "interface", InterfaceListConfigParser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "subnet6", Subnets6ListConfigParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "version", StringParser::Factory));
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet6"] = Subnets6ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1194,25 +1676,12 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
return (f->second(config_id));
}
-/// @brief configures DHCPv6 server
-///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
-///
-/// This method does not throw. It catches all exceptions and returns them as
-/// reconfiguration statuses. It may return the following response codes:
-/// 0 - configuration successful
-/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
-///
-/// @param config_set a new configuration for DHCPv6 server
-/// @return answer that contains result of reconfiguration
ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
if (!config_set) {
- isc_throw(Dhcp6ConfigError,
- "Null pointer is passed to configuration parser");
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Can't parse NULL config"));
+ return (answer);
}
/// @todo: append most essential info here (like "2 new subnets configured")
@@ -1220,42 +1689,126 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
try {
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ if (config_pair.first == "subnet6") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
}
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet6");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
+ }
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed:") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed:")
+ + ex.what());
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed:") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 9f7c3ae..6d7a807 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,11 @@
#ifndef DHCP6_CONFIG_PARSER_H
#define DHCP6_CONFIG_PARSER_H
+/// @todo: This header file and its .cc counterpart are very similar between
+/// DHCPv4 and DHCPv6. They should be merged. See ticket #2355.
+
#include <cc/data.h>
#include <exceptions/exceptions.h>
-
#include <string>
namespace isc {
@@ -25,122 +27,25 @@ namespace dhcp {
class Dhcpv6Srv;
-/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv6Srv object.
-class Dhcp6ConfigError : public isc::Exception {
-public:
-
-/// @brief constructor
-///
-/// @param file name of the file, where exception occurred
-/// @param line line of the file, where exception occurred
-/// @param what text description of the issue that caused exception
-Dhcp6ConfigError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-class DhcpConfigParser {
- ///
- /// \name Constructors and Destructor
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
- DhcpConfigParser(const DhcpConfigParser& source);
- DhcpConfigParser& operator=(const DhcpConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- DhcpConfigParser() {}
-public:
- /// The destructor.
- virtual ~DhcpConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp6Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
+/// @brief Configures DHCPv6 server
///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c Dhcp6ConfigError.
-/// If the given configuration requires resource allocation and it fails,
-/// a corresponding standard exception will be thrown.
-/// Other exceptions may also be thrown, depending on the implementation of
-/// the underlying derived class of \c Dhcp6ConfigError.
-/// In any case the strong guarantee is provided as described above except
-/// in the very rare cases where the \c commit() method of a parser throws
-/// an exception. If that happens this function converts the exception
-/// into a \c FatalError exception and rethrows it. This exception is
-/// expected to be caught at the highest level of the application to terminate
-/// the program gracefully.
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but the values could not be
+/// stored in the configuration).
///
-/// \param server The \c Dhcpv6Srv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
+/// @param server DHCPv6 server object.
+/// @param config_set a new configuration for DHCPv6 server.
+/// @return answer that contains result of the reconfiguration.
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& server,
- isc::data::ConstElementPtr config_set);
+configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 5d4d990..ba3e2c2 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -17,9 +17,9 @@
#include <asiolink/asiolink.h>
#include <cc/data.h>
#include <cc/session.h>
-#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
@@ -122,7 +122,7 @@ void ControlledDhcpv6Srv::establishSession() {
try {
configureDhcp6Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp6ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index 22cb3b6..ef1acab 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -68,8 +68,8 @@ public:
/// @brief Session callback, processes received commands.
///
- /// @param command_id text represenation of the command (e.g. "shutdown")
- /// @param args optional parameters
+ /// @param command Text represenation of the command (e.g. "shutdown")
+ /// @param args Optional parameters
///
/// @return status of the command
static isc::data::ConstElementPtr
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index c234f40..fa37769 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -1,5 +1,5 @@
/**
- @page dhcpv6 DHCPv6 Server Component
+ @page dhcp6 DHCPv6 Server Component
BIND10 offers DHCPv6 server implementation. It is implemented as
b10-dhcp6 component. Its primary code is located in
@@ -16,13 +16,13 @@
DHCPv6 server component does not use BIND10 logging yet.
- @section dhcpv6-session BIND10 message queue integration
+ @section dhcpv6Session BIND10 message queue integration
DHCPv4 server component is now integrated with BIND10 message queue.
It follows the same principle as DHCPv4. See \ref dhcpv4Session for
details.
- @section dhcpv6-config-parser Configuration Parser in DHCPv6
+ @section dhcpv6ConfigParser Configuration Parser in DHCPv6
b10-dhcp6 component uses BIND10 cfgmgr for commands and configuration. During
initial configuration (See \ref
@@ -35,36 +35,36 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
+ from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
- are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
- unsigned 32 bit integer), but some are very specialized (e.g. \ref
- isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
- some cases, e.g. subnet6 definitions, the configuration entry is not a simple
- value, but a map or a list itself. In such case, the parser iterates over all
- elements and creates parsers for a given scope. This process may be repeated
- (sort of) recursively.
+ are generic (e.g. Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g.
+ Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
+ e.g. subnet6 definitions, the configuration entry is not a simple value, but
+ a map or a list itself. In such case, the parser iterates over all elements
+ and creates parsers for a given scope. This process may be repeated (sort of)
+ recursively.
- @section dhcpv6-config-inherit DHCPv6 Configuration Inheritance
+ @section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
One notable useful feature of DHCP configuration is its parameter inheritance.
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
- defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
- isc::dhcp::StringParser) implement that inheritance. By default, they store
- values in global uint32_defaults and string_defaults storages. However, it is
- possible to instruct them to store parsed values in more specific
- storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
- that has its own storage that is unique for each subnet. Finally, during commit
- phase (commit() method), appropriate parsers can use apply parameter inheritance.
+ defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
+ inheritance. By default, they store values in global uint32_defaults and
+ string_defaults storages. However, it is possible to instruct them to store
+ parsed values in more specific storages. That capability is used, e.g. in
+ Subnet6ConfigParser that has its own storage that is unique for each subnet.
+ Finally, during commit phase (commit() method), appropriate parsers can use
+ apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
+ class called DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index c5e9565..7f80457 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -40,6 +40,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -67,6 +117,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
},
@@ -89,6 +149,12 @@
"item_default": ""
},
+ { "item_name": "interface",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
@@ -152,6 +218,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
} ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6b82344..83f75d9 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -22,6 +22,11 @@ successfully established a session with the BIND 10 control channel.
This debug message is issued just before the IPv6 DHCP server attempts
to establish a session with the BIND 10 control channel.
+% DHCP6_CLIENTID_MISSING mandatory client-id option is missing, message from %1 dropped
+This error message indicates that the received message is being dropped
+because it does not include the mandatory client-id option necessary for
+address assignment. The most likely cause is a problem with the client.
+
% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the BIND 10 control system by the IPv6 DHCP server.
@@ -42,9 +47,9 @@ This is an informational message reporting that the configuration has
been extended to include the specified subnet.
% DHCP6_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
-This warning message is issued on attempt to configure multiple options with the
+This warning message is issued on an attempt to configure multiple options with the
same option code for the particular subnet. Adding multiple options is uncommon
-for DHCPv6, yet it is not prohibited.
+for DHCPv6, but it is not prohibited.
% DHCP6_CONFIG_START DHCPv6 server is processing the following configuration: %1
This is a debug message that is issued every time the server receives a
@@ -56,15 +61,22 @@ A debug message indicating that the IPv6 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP6_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
-This informational message is printed every time DHCPv6 is started.
-It indicates what database backend type is being to store lease and
-other information.
+This informational message is printed every time the IPv6 DHCP server
+is started. It indicates what database backend type is being to store
+lease and other information.
+
+% DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
+This error message indicates a database consistency failure. The lease
+database has an entry indicating that the given address is in use,
+but the lease does not contain any client identification. This is most
+likely due to a software error: please raise a bug report. As a temporary
+workaround, manually remove the lease entry from the database.
% DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
This debug message indicates that the server successfully advertised
-a lease. It is up to the client to choose one server out of othe advertised
-and continue allocation with that server. This is a normal behavior and
-indicates successful operation.
+a lease. It is up to the client to choose one server out of the
+advertised servers and continue allocation with that server. This
+is a normal behavior and indicates successful operation.
% DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2
This message indicates that the server failed to advertise (in response to
@@ -74,13 +86,44 @@ such failure. Each specific failure is logged in a separate log entry.
% DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3)
This debug message indicates that the server successfully granted (in
response to client's REQUEST message) a lease. This is a normal behavior
-and incicates successful operation.
+and indicates successful operation.
% DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2
This message indicates that the server failed to grant (in response to
received REQUEST) a lease for a given client. There may be many reasons for
such failure. Each specific failure is logged in a separate log entry.
+% DHCP6_RELEASE address %1 belonging to client duid=%2, iaid=%3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP6_RELEASE_FAIL failed to remove lease for address %1 for duid=%2, iaid=%3
+This error message indicates that the software failed to remove a
+lease from the lease database. It probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP6_RELEASE_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to client (duid=%3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -116,7 +159,7 @@ a received OFFER packet as UNKNOWN).
% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv6 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP6_PACKET_SEND_FAIL failed to send DHCPv6 packet: %1
@@ -137,6 +180,13 @@ as a hint for possible requested address.
% DHCP6_QUERY_DATA received packet length %1, data length %2, data is %3
A debug message listing the data received from the client or relay.
+% DHCP6_REQUIRED_OPTIONS_CHECK_FAIL %1 message received from %2 failed the following check: %3
+This message indicates that received DHCPv6 packet is invalid. This may be due
+to a number of reasons, e.g. the mandatory client-id option is missing,
+the server-id forbidden in that particular type of message is present,
+there is more than one instance of client-id or server-id present,
+etc. The exact reason for rejecting the packet is included in the message.
+
% DHCP6_RESPONSE_DATA responding with packet type %1 data is %2
A debug message listing the data returned to the client.
@@ -189,3 +239,23 @@ which the DHCPv6 server has not been configured. The cause is most likely due
to a misconfiguration of the server. The packet processing will continue, but
the response will only contain generic configuration parameters and no
addresses or prefixes.
+
+% DHCP6_UNKNOWN_RENEW received RENEW from client (duid=%1, iaid=%2) in subnet %3
+This warning message is printed when client attempts to renew a lease,
+but no such lease is known by the server. It typically means that
+client has attempted to use its lease past its lifetime: causes of this
+include a adjustment of the clients date/time setting or poor support
+for sleep/recovery. A properly implemented client will recover from such
+a situation by restarting the lease allocation process after receiving
+a negative reply from the server.
+
+An alternative cause could be that the server has lost its database
+recently and does not recognize its well-behaving clients. This is more
+probable if you see many such messages. Clients will recover from this,
+but they will most likely get a different IP addresses and experience
+a brief service interruption.
+
+% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
+This warning message is printed when client attempts to release a lease,
+but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
+possible reasons for such behavior.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 2a5798d..8fb55ec 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -23,7 +23,8 @@
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int_array.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
@@ -31,6 +32,7 @@
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
@@ -56,12 +58,6 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
// Initialize objects required for DHCP server operation.
try {
- // Initialize standard DHCPv6 option definitions. This function
- // may throw bad_alloc if system goes out of memory during the
- // creation if option definitions. It may also throw isc::Unexpected
- // if definitions are wrong. This would mean error in implementation.
- initStdOptionDefs();
-
// Port 0 is used for testing purposes. It means that the server should
// not open any sockets at all. Some tests, e.g. configuration parser,
// require Dhcpv6Srv object, but they don't really need it to do
@@ -132,50 +128,57 @@ bool Dhcpv6Srv::run() {
continue;
}
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_PACKET_RECEIVED)
- .arg(serverReceivedPacketName(query->getType()));
+ .arg(query->getName());
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_QUERY_DATA)
.arg(static_cast<int>(query->getType()))
.arg(query->getBuffer().getLength())
.arg(query->toText());
- switch (query->getType()) {
- case DHCPV6_SOLICIT:
- rsp = processSolicit(query);
- break;
+ try {
+ switch (query->getType()) {
+ case DHCPV6_SOLICIT:
+ rsp = processSolicit(query);
+ break;
- case DHCPV6_REQUEST:
- rsp = processRequest(query);
- break;
+ case DHCPV6_REQUEST:
+ rsp = processRequest(query);
+ break;
- case DHCPV6_RENEW:
- rsp = processRenew(query);
- break;
+ case DHCPV6_RENEW:
+ rsp = processRenew(query);
+ break;
- case DHCPV6_REBIND:
- rsp = processRebind(query);
- break;
+ case DHCPV6_REBIND:
+ rsp = processRebind(query);
+ break;
- case DHCPV6_CONFIRM:
- rsp = processConfirm(query);
- break;
+ case DHCPV6_CONFIRM:
+ rsp = processConfirm(query);
+ break;
- case DHCPV6_RELEASE:
+ case DHCPV6_RELEASE:
rsp = processRelease(query);
break;
- case DHCPV6_DECLINE:
- rsp = processDecline(query);
- break;
+ case DHCPV6_DECLINE:
+ rsp = processDecline(query);
+ break;
- case DHCPV6_INFORMATION_REQUEST:
- rsp = processInfRequest(query);
- break;
+ case DHCPV6_INFORMATION_REQUEST:
+ rsp = processInfRequest(query);
+ break;
- default:
- // Only action is to output a message if debug is enabled,
- // and that will be covered by the debug statement before
- // the "switch" statement.
- ;
+ default:
+ // Only action is to output a message if debug is enabled,
+ // and that will be covered by the debug statement before
+ // the "switch" statement.
+ ;
+ }
+ } catch (const RFCViolation& e) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_REQUIRED_OPTIONS_CHECK_FAIL)
+ .arg(query->getName())
+ .arg(query->getRemoteAddr())
+ .arg(e.what());
}
if (rsp) {
@@ -201,9 +204,6 @@ bool Dhcpv6Srv::run() {
}
}
}
-
- // TODO add support for config session (see src/bin/auth/main.cc)
- // so this daemon can be controlled from bob
}
return (true);
@@ -332,8 +332,8 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
// Client requests some options using ORO option. Try to
// get this option from client's message.
- boost::shared_ptr<Option6IntArray<uint16_t> > option_oro =
- boost::dynamic_pointer_cast<Option6IntArray<uint16_t> >(question->getOption(D6O_ORO));
+ boost::shared_ptr<OptionIntArray<uint16_t> > option_oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(question->getOption(D6O_ORO));
// Option ORO not found. Don't do anything then.
if (!option_oro) {
return;
@@ -341,8 +341,8 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
// Get the list of options configured for a subnet.
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to match requested options with those configured for a subnet.
// If match is found, append configured option to the answer message.
BOOST_FOREACH(uint16_t opt, requested_opts) {
@@ -354,17 +354,91 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
}
OptionPtr Dhcpv6Srv::createStatusCode(uint16_t code, const std::string& text) {
+ // @todo This function uses OptionCustom class to manage contents
+ // of the data fields. Since this this option is frequently used
+ // it may be good to implement dedicated class to avoid performance
+ // impact.
+
+ // Get the definition of the option holding status code.
+ OptionDefinitionPtr status_code_def =
+ LibDHCP::getOptionDef(Option::V6, D6O_STATUS_CODE);
+ // This definition is assumed to be initialized in LibDHCP.
+ assert(status_code_def);
+
+ // As there is no dedicated class to represent Status Code
+ // the OptionCustom class should be returned here.
+ boost::shared_ptr<OptionCustom> option_status =
+ boost::dynamic_pointer_cast<
+ OptionCustom>(status_code_def->optionFactory(Option::V6, D6O_STATUS_CODE));
+ assert(option_status);
+
+ // Set status code to 'code' (0 - means data field #0).
+ option_status->writeInteger(code, 0);
+ // Set a message (1 - means data field #1).
+ option_status->writeString(text, 1);
+ return (option_status);
+}
- // @todo: Implement Option6_StatusCode and rewrite this code here
- vector<uint8_t> data(text.c_str(), text.c_str() + text.length());
- data.insert(data.begin(), static_cast<uint8_t>(code % 256));
- data.insert(data.begin(), static_cast<uint8_t>(code >> 8));
- OptionPtr status(new Option(Option::V6, D6O_STATUS_CODE, data));
- return (status);
+void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+ RequirementLevel serverid) {
+ Option::OptionCollection client_ids = pkt->getOptions(D6O_CLIENTID);
+ switch (clientid) {
+ case MANDATORY:
+ if (client_ids.size() != 1) {
+ isc_throw(RFCViolation, "Exactly 1 client-id option expected in "
+ << pkt->getName() << ", but " << client_ids.size()
+ << " received");
+ }
+ break;
+ case OPTIONAL:
+ if (client_ids.size() > 1) {
+ isc_throw(RFCViolation, "Too many (" << client_ids.size()
+ << ") client-id options received in " << pkt->getName());
+ }
+ break;
+
+ case FORBIDDEN:
+ // doesn't make sense - client-id is always allowed
+ break;
+ }
+
+ Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (!server_ids.empty()) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
+ << server_ids.size() << " received in " << pkt->getName());
+ }
+ break;
+
+ case MANDATORY:
+ if (server_ids.size() != 1) {
+ isc_throw(RFCViolation, "Invalid number of server-id options received ("
+ << server_ids.size() << "), exactly 1 expected in message "
+ << pkt->getName());
+ }
+ break;
+
+ case OPTIONAL:
+ if (server_ids.size() > 1) {
+ isc_throw(RFCViolation, "Too many (" << server_ids.size()
+ << ") server-id options received in " << pkt->getName());
+ }
+ }
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
- Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+ /// @todo: pass interface information only if received direct (non-relayed) message
+
+ // Try to find a subnet if received packet from a directly connected client
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
+ if (subnet) {
+ return (subnet);
+ }
+
+ // If no subnet was found, try to find it based on remote address
+ subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
return (subnet);
}
@@ -373,10 +447,12 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(question);
- if (subnet) {
+ if (!subnet) {
// This particular client is out of luck today. We do not have
// information about the subnet he is connected to. This likely means
// misconfiguration of the server (or some relays). We will continue to
@@ -384,12 +460,16 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// addresses or prefixes, no subnet specific configuration etc. The only
// thing this client can get is some global information (like DNS
// servers).
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
- .arg(subnet->toText());
- } else {
+
// perhaps this should be logged on some higher level? This is most likely
// configuration bug.
- LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_SUBNET_SELECTION_FAILED);
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(question->getName());
+
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
}
// @todo: We should implement Option6Duid some day, but we can do without it
@@ -403,6 +483,10 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
OptionPtr opt_duid = question->getOption(D6O_CLIENTID);
if (opt_duid) {
duid = DuidPtr(new DUID(opt_duid->getData()));
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_BASIC, DHCP6_CLIENTID_MISSING);
+ // Let's drop the message. This client is not sane.
+ isc_throw(RFCViolation, "Mandatory client-id is missing in received message");
}
// Now that we have all information about the client, let's iterate over all
@@ -415,7 +499,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
opt != question->options_.end(); ++opt) {
switch (opt->second->getType()) {
case D6O_IA_NA: {
- OptionPtr answer_opt = handleIA_NA(subnet, duid, question,
+ OptionPtr answer_opt = assignIA_NA(subnet, duid, question,
boost::dynamic_pointer_cast<Option6IA>(opt->second));
if (answer_opt) {
answer->addOption(answer_opt);
@@ -428,14 +512,18 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
}
}
-OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid, Pkt6Ptr question,
- boost::shared_ptr<Option6IA> ia) {
+OptionPtr Dhcpv6Srv::assignIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
// If there is no subnet selected for handling this IA_NA, the only thing to do left is
// to say that we are sorry, but the user won't get an address. As a convenience, we
// use a different status text to indicate that (compare to the same status code,
// but different wording below)
if (!subnet) {
// Create empty IA_NA option with IAID matching the request.
+ // Note that we don't use OptionDefinition class to create this option.
+ // This is because we prefer using a constructor of Option6IA that
+ // initializes IAID. Otherwise we would have to use setIAID() after
+ // creation of the option which has some performance implications.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
@@ -477,6 +565,8 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
hint, fake_allocation);
// Create IA_NA that we will put in the response.
+ // Do not use OptionDefinition to create option's instance so
+ // as we can initialize IAID using a constructor.
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
if (lease) {
@@ -518,8 +608,278 @@ OptionPtr Dhcpv6Srv::handleIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
return (ia_rsp);
}
+OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia) {
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(*duid, ia->getIAID(),
+ subnet->getID());
+
+ if (!lease) {
+ // client renewing a lease that we don't know about.
+
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "Sorry, no known leases for this duid/iaid."));
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
+ .arg(duid->toText())
+ .arg(ia->getIAID())
+ .arg(subnet->toText());
+
+ return (ia_rsp);
+ }
+
+ lease->preferred_lft_ = subnet->getPreferred();
+ lease->valid_lft_ = subnet->getValid();
+ lease->t1_ = subnet->getT1();
+ lease->t2_ = subnet->getT2();
+ lease->cltt_ = time(NULL);
+
+ LeaseMgrFactory::instance().updateLease6(lease);
+
+ // Create empty IA_NA option with IAID matching the request.
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ ia_rsp->setT1(subnet->getT1());
+ ia_rsp->setT2(subnet->getT2());
+
+ boost::shared_ptr<Option6IAAddr> addr(new Option6IAAddr(D6O_IAADDR,
+ lease->addr_, lease->preferred_lft_,
+ lease->valid_lft_));
+ ia_rsp->addOption(addr);
+ return (ia_rsp);
+}
+
+void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
+
+ // We need to renew addresses for all IA_NA options in the client's
+ // RENEW message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
+
+ // We need to select a subnet the client is connected in.
+ Subnet6Ptr subnet = selectSubnet(renew);
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
+ .arg(subnet->toText());
+ }
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = renew->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ for (Option::OptionCollection::iterator opt = renew->options_.begin();
+ opt != renew->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = renewIA_NA(subnet, duid, renew,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+}
+
+void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+
+ // We need to release addresses for all IA_NA options in the client's
+ // RELEASE message.
+ // @todo Add support for IA_TA
+ // @todo Add support for IA_PD
+ // @todo Consider supporting more than one address in a single IA_NA.
+ // That was envisaged by RFC3315, but it never happened. The only
+ // software that supports that is Dibbler, but its author seriously doubts
+ // if anyone is really using it. Clients that want more than one address
+ // just include more instances of IA_NA options.
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ // see sanityCheck() called from processRelease()
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
+ .arg(release->getRemoteAddr().toText());
+
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ int general_status = STATUS_Success;
+ for (Option::OptionCollection::iterator opt = release->options_.begin();
+ opt != release->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ // @todo: add support for IA_PD
+ // @todo: add support for IA_TA
+ default:
+ // remaining options are stateless and thus ignored in this context
+ ;
+ }
+ }
+
+ // To be pedantic, we should also include status code in the top-level
+ // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
+ // This behavior will likely go away in RFC3315bis.
+ reply->addOption(createStatusCode(general_status,
+ "Summary status for all processed IA_NAs"));
+}
+
+OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia) {
+ // Release can be done in one of two ways:
+ // Approach 1: extract address from client's IA_NA and see if it belongs
+ // to this particular client.
+ // Approach 2: find a subnet for this client, get a lease for
+ // this subnet/duid/iaid and check if its content matches to what the
+ // client is asking us to release.
+ //
+ // This method implements approach 1.
+
+ // That's our response
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ if (!release_addr) {
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "You did not include address in your RELEASE"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());
+
+ if (!lease) {
+ // client releasing a lease that we don't know about.
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "Sorry, no known leases for this duid/iaid, can't release."));
+ general_status = STATUS_NoBinding;
+
+ LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
+ .arg(duid->toText())
+ .arg(ia->getIAID());
+
+ return (ia_rsp);
+ }
+
+ if (!lease->duid_) {
+ // Something is gravely wrong here. We do have a lease, but it does not
+ // have mandatory DUID information attached. Someone was messing with our
+ // database.
+
+ LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
+ .arg(release_addr->getAddress().toText());
+
+ general_status = STATUS_UnspecFail;
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Database consistency check failed when trying to RELEASE"));
+ return (ia_rsp);
+ }
+
+ if (*duid != *(lease->duid_)) {
+ // Sorry, it's not your address. You can't release it.
+
+ LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->duid_->toText());
+
+ general_status = STATUS_NoBinding;
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This address does not belong to you, you can't release it"));
+ return (ia_rsp);
+ }
+
+ if (ia->getIAID() != lease->iaid_) {
+ // This address belongs to this client, but to a different IA
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->iaid_)
+ .arg(ia->getIAID());
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This is your address, but you used wrong IAID"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ // It is not necessary to check if the address matches as we used
+ // getLease6(addr) method that is supposed to return a proper lease.
+
+ // Ok, we've passed all checks. Let's release this address.
+
+ if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Server failed to release a lease"));
+
+ LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+ general_status = STATUS_UnspecFail;
+
+ return (ia_rsp);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+
+ ia_rsp->addOption(createStatusCode(STATUS_Success,
+ "Lease released. Thank you, please come again."));
+
+ return (ia_rsp);
+ }
+}
+
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
+ sanityCheck(solicit, MANDATORY, FORBIDDEN);
+
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
@@ -532,6 +892,9 @@ Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
}
Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
+
+ sanityCheck(request, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, request->getTransid()));
copyDefaultOptions(request, reply);
@@ -544,8 +907,17 @@ Pkt6Ptr Dhcpv6Srv::processRequest(const Pkt6Ptr& request) {
}
Pkt6Ptr Dhcpv6Srv::processRenew(const Pkt6Ptr& renew) {
- /// @todo: Implement this
+
+ sanityCheck(renew, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, renew->getTransid()));
+
+ copyDefaultOptions(renew, reply);
+ appendDefaultOptions(renew, reply);
+ appendRequestedOptions(renew, reply);
+
+ renewLeases(renew, reply);
+
return reply;
}
@@ -562,8 +934,16 @@ Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
}
Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
- /// @todo: Implement this
+
+ sanityCheck(release, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
+
+ copyDefaultOptions(release, reply);
+ appendDefaultOptions(release, reply);
+
+ releaseLeases(release, reply);
+
return reply;
}
@@ -579,53 +959,5 @@ Pkt6Ptr Dhcpv6Srv::processInfRequest(const Pkt6Ptr& infRequest) {
return reply;
}
-const char*
-Dhcpv6Srv::serverReceivedPacketName(uint8_t type) {
- static const char* CONFIRM = "CONFIRM";
- static const char* DECLINE = "DECLINE";
- static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
- static const char* REBIND = "REBIND";
- static const char* RELEASE = "RELEASE";
- static const char* RENEW = "RENEW";
- static const char* REQUEST = "REQUEST";
- static const char* SOLICIT = "SOLICIT";
- static const char* UNKNOWN = "UNKNOWN";
-
- switch (type) {
- case DHCPV6_CONFIRM:
- return (CONFIRM);
-
- case DHCPV6_DECLINE:
- return (DECLINE);
-
- case DHCPV6_INFORMATION_REQUEST:
- return (INFORMATION_REQUEST);
-
- case DHCPV6_REBIND:
- return (REBIND);
-
- case DHCPV6_RELEASE:
- return (RELEASE);
-
- case DHCPV6_RENEW:
- return (RENEW);
-
- case DHCPV6_REQUEST:
- return (REQUEST);
-
- case DHCPV6_SOLICIT:
- return (SOLICIT);
-
- default:
- ;
- }
- return (UNKNOWN);
-}
-
-void
-Dhcpv6Srv::initStdOptionDefs() {
- LibDHCP::initStdOptionDefs(Option::V6);
-}
-
};
};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b4ce8b1..30abb50 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -30,6 +30,7 @@
namespace isc {
namespace dhcp {
+
/// @brief DHCPv6 server service.
///
/// This class represents DHCPv6 server. It contains all
@@ -45,6 +46,12 @@ namespace dhcp {
class Dhcpv6Srv : public boost::noncopyable {
public:
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
/// @brief Minimum length of a MAC address to be used in DUID generation.
static const size_t MIN_MAC_LEN = 6;
@@ -60,7 +67,7 @@ public:
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
Dhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
@@ -83,24 +90,20 @@ public:
/// @brief Instructs the server to shut down.
void shutdown();
- /// @brief Return textual type of packet received by server.
- ///
- /// Returns the name of valid packet received by the server (e.g. SOLICIT).
- /// If the packet is unknown - or if it is a valid DHCP packet but not one
- /// expected to be received by the server (such as an ADVERTISE), the string
- /// "UNKNOWN" is returned. This method is used in debug messages.
- ///
- /// As the operation of the method does not depend on any server state, it
- /// is declared static.
+protected:
+
+ /// @brief verifies if specified packet meets RFC requirements
///
- /// @param type DHCPv4 packet type
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
///
- /// @return Pointer to "const" string containing the packet name.
- /// Note that this string is statically allocated and MUST NOT
- /// be freed by the caller.
- static const char* serverReceivedPacketName(uint8_t type);
+ /// @param pkt packet to be checked
+ /// @param clientid expectation regarding client-id option
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ void sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
+ RequirementLevel serverid);
-protected:
/// @brief Processes incoming SOLICIT and returns response.
///
/// Processes received SOLICIT message and verifies that its sender
@@ -186,11 +189,46 @@ protected:
/// @param question client's message (typically SOLICIT or REQUEST)
/// @param ia pointer to client's IA_NA option (client's request)
/// @return IA_NA option (server's response)
- OptionPtr handleIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
+ OptionPtr assignIA_NA(const isc::dhcp::Subnet6Ptr& subnet,
const isc::dhcp::DuidPtr& duid,
isc::dhcp::Pkt6Ptr question,
boost::shared_ptr<Option6IA> ia);
+ /// @brief Renews specific IA_NA option
+ ///
+ /// Generates response to IA_NA in Renew. This typically includes finding a
+ /// lease that corresponds to the received address. If no such lease is
+ /// found, an IA_NA response is generated with an appropriate status code.
+ ///
+ /// @param subnet subnet the sender belongs to
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
+ OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+
+ /// @brief Releases specific IA_NA option
+ ///
+ /// Generates response to IA_NA in Release message. This covers finding and
+ /// removal of a lease that corresponds to the received address. If no such
+ /// lease is found, an IA_NA response is generated with an appropriate
+ /// status code.
+ ///
+ /// As RFC 3315 requires that a single status code be sent for the whole message,
+ /// this method may update the passed general_status: it is set to SUCCESS when
+ /// message processing begins, but may be updated to some error code if the
+ /// release process fails.
+ ///
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param general_status a global status (it may be updated in case of errors)
+ /// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
+ OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia);
+
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -221,14 +259,35 @@ protected:
/// @brief Assigns leases.
///
- /// TODO: This method is currently a stub. It just appends one
- /// hardcoded lease. It supports addresses (IA_NA) only. It does NOT
- /// support temporary addresses (IA_TA) nor prefixes (IA_PD).
+ /// It supports addresses (IA_NA) only. It does NOT support temporary
+ /// addresses (IA_TA) nor prefixes (IA_PD).
+ /// @todo: Extend this method once TA and PD becomes supported
///
/// @param question client's message (with requested IA_NA)
/// @param answer server's message (IA_NA options will be added here)
void assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer);
+ /// @brief Attempts to renew received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to renew
+ /// received addresses. If no such leases are found, proper status
+ /// code is added to reply message. Renewed addresses are added
+ /// as IA_NA/IAADDR to reply packet.
+ /// @param renew client's message asking for renew
+ /// @param reply server's response
+ void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+
+ /// @brief Attempts to release received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to release
+ /// received addresses. If no such leases are found, or the lease fails
+ /// proper checks (e.g. belongs to someone else), a proper status
+ /// code is added to reply message. Released addresses are not added
+ /// to REPLY packet, just its IA_NA containers.
+ /// @param release client's message asking to release
+ /// @param reply server's response
+ void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+
/// @brief Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
@@ -241,17 +300,6 @@ protected:
/// interfaces for new DUID generation are detected.
void setServerID();
- /// @brief Initializes option definitions for standard options.
- ///
- /// Each standard option's format is described by the
- /// dhcp::OptionDefinition object. This function creates such objects
- /// for each standard DHCPv6 option.
- ///
- /// @todo list thrown exceptions.
- /// @todo extend this function to cover all standard options. Currently
- /// it is limited to critical options only.
- void initStdOptionDefs();
-
private:
/// @brief Allocation Engine.
/// Pointer to the allocation engine that we are currently using
@@ -260,7 +308,7 @@ private:
boost::shared_ptr<AllocEngine> alloc_engine_;
/// Server DUID (to be sent in server-identifier option)
- boost::shared_ptr<isc::dhcp::Option> serverid_;
+ OptionPtr serverid_;
/// Indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
diff --git a/src/bin/dhcp6/main.cc b/src/bin/dhcp6/main.cc
index ae299e5..ced7422 100644
--- a/src/bin/dhcp6/main.cc
+++ b/src/bin/dhcp6/main.cc
@@ -17,6 +17,7 @@
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <log/logger_support.h>
+#include <log/logger_manager.h>
#include <boost/lexical_cast.hpp>
@@ -103,9 +104,10 @@ main(int argc, char* argv[]) {
}
// Initialize logging. If verbose, we'll use maximum verbosity.
+ // If standalone is enabled, do not buffer initial log messages
isc::log::initLogger(DHCP6_NAME,
(verbose_mode ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, !stand_alone);
LOG_INFO(dhcp6_logger, DHCP6_STARTING);
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_START_INFO)
.arg(getpid()).arg(port_number).arg(verbose_mode ? "yes" : "no")
@@ -119,8 +121,12 @@ main(int argc, char* argv[]) {
server.establishSession();
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_SESSION_FAIL).arg(ex.what());
- // Let's continue. It is useful to have the ability to run
+ // Let's continue. It is useful to have the ability to run
// DHCP server in stand-alone mode, e.g. for testing
+ // We do need to make sure logging is no longer buffered
+ // since then it would not print until dhcp6 is stopped
+ isc::log::LoggerManager log_manager;
+ log_manager.process();
}
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_STANDALONE);
diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am
index b01b877..d251df3 100644
--- a/src/bin/dhcp6/tests/Makefile.am
+++ b/src/bin/dhcp6/tests/Makefile.am
@@ -63,14 +63,13 @@ dhcp6_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
dhcp6_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
dhcp6_unittests_LDADD = $(GTEST_LDADD)
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/libb10-dhcpsrv.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
dhcp6_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/config/libb10-cfgclient.la
-dhcp6_unittests_LDADD += $(top_builddir)/src/lib/cc/libb10-cc.la
-
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/log/libb10-log.la
+dhcp6_unittests_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 2064e72..b3fe88e 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
@@ -47,10 +48,22 @@ public:
// deal with sockets here, just check if configuration handling
// is sane.
- // Create instances of option definitions and put them into storage.
- // This is normally initialized by the server when calling run()
- // run() function.
- LibDHCP::initStdOptionDefs(Option::V6);
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = ifaces.begin()->getName();
+ bogus_iface_ = "nonexisting0";
+
+ if (IfaceMgr::instance().getIface(bogus_iface_)) {
+ ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
+ << " while the test assumes that it doesn't, to execute"
+ << " some negative scenarios. Can't continue this test.";
+ }
}
~Dhcp6ParserTest() {
@@ -58,10 +71,20 @@ public:
resetConfiguration();
};
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data" and
+ /// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -72,21 +95,41 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["code"] = "80";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "subscriber-id";
+ params["space"] = param_value;
+ params["code"] = "38";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
- params["code"] = "80";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
- std::string createConfigWithOption(const std::map<std::string, std::string>& params) {
+ std::string createConfigWithOption(const std::map<std::string,
+ std::string>& params)
+ {
std::ostringstream stream;
stream << "{ \"interface\": [ \"all\" ],"
"\"preferred-lifetime\": 3000,"
@@ -102,14 +145,19 @@ public:
if (!first) {
stream << ", ";
} else {
+ // cppcheck-suppress unreadVariable
first = false;
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
- stream << "\"code\": " << param.second << "";
+ stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -135,6 +183,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -149,14 +198,14 @@ public:
<< ex.what() << std::endl;
}
-
- // returned value should be 0 (configuration success)
+ // status object must not be NULL
if (!status) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
<< " NULL pointer" << std::endl;
}
comment_ = parseAnswer(rcode_, status);
+ // returned value should be 0 (configuration success)
if (rcode_ != 0) {
FAIL() << "Fatal error: unable to reset configuration database"
<< " after the test. Configuration function returned"
@@ -220,15 +269,19 @@ public:
ASSERT_EQ(buf.getLength() - option_desc.option->getHeaderLen(),
expected_data_len);
}
- // Verify that the data is correct. However do not verify suboptions.
+ // Verify that the data is correct. Do not verify suboptions and a header.
const uint8_t* data = static_cast<const uint8_t*>(buf.getData());
- EXPECT_TRUE(memcmp(expected_data, data, expected_data_len));
+ EXPECT_EQ(0, memcmp(expected_data, data + option_desc.option->getHeaderLen(),
+ expected_data_len));
}
Dhcpv6Srv srv_;
int rcode_;
ConstElementPtr comment_;
+
+ string valid_iface_;
+ string bogus_iface_;
};
// Goal of this test is a verification if a very simple config update
@@ -262,9 +315,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
EXPECT_EQ(1, rcode_);
}
-/// The goal of this test is to verify if wrongly defined subnet will
-/// be rejected. Properly defined subnet must include at least one
-/// pool definition.
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
@@ -355,6 +407,99 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
EXPECT_EQ(4, subnet->getValid());
}
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(valid_iface_, subnet->getIface());
+}
+
+// This test checks if invalid interface name will be rejected in
+// Subnet6 definition.
+TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + bogus_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 1 (configuration error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ EXPECT_FALSE(subnet);
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 1 (parse error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+}
+
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
@@ -375,11 +520,11 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
- EXPECT_EQ(2, rcode_);
+ EXPECT_EQ(1, rcode_);
}
// Goal of this test is to verify if pools can be defined
@@ -416,6 +561,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space) and that it is allowed to define option in the dhcp6
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 100 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp6 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp6' option space groups
+ // standard options and the code 3 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 3,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -426,14 +928,18 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": True"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -450,42 +956,112 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
// Check that options with other option codes are not returned.
- for (uint16_t code = 102; code < 110; ++code) {
+ for (uint16_t code = 47; code < 57; ++code) {
range = idx.equal_range(code);
EXPECT_EQ(0, std::distance(range.first, range.second));
}
}
-// Goal of this test is to verify options configuration
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp6' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 38,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp6.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
+ ASSERT_FALSE(desc3.option);
+}
+
+// The goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
@@ -496,22 +1072,28 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -525,33 +1107,35 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
}
// Goal of this test is to verify options configuration
@@ -566,18 +1150,22 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"0102030405060708090A\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
" \"pool\": [ \"2001:db8:2::/80\" ],"
" \"subnet\": \"2001:db8:2::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"FFFEFDFCFB\""
+ " \"name\": \"user-class\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 15,"
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -591,43 +1179,45 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet1);
- const Subnet::OptionContainer& options1 = subnet1->getOptions();
- ASSERT_EQ(1, options1.size());
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range1 =
- idx1.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A
};
// Check if option is valid in terms of code and carried data.
- testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
// Test another subnet in the same way.
Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
ASSERT_TRUE(subnet2);
- const Subnet::OptionContainer& options2 = subnet2->getOptions();
- ASSERT_EQ(1, options2.size());
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options2->size());
- const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
- idx2.equal_range(101);
+ idx2.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
- const uint8_t foo2_expected[] = {
+ const uint8_t user_class_expected[] = {
0xFF, 0xFE, 0xFD, 0xFC, 0xFB
};
- testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
}
// Verify that empty option name is rejected in the configuration.
@@ -709,25 +1299,26 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(80);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x0A, 0x0B, 0x0C, 0x0D
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
}
// Verify that specific option object is returned for standard
@@ -735,10 +1326,12 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
TEST_F(Dhcp6ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
- params["name"] = "OPTION_IA_NA";
+ params["name"] = "ia-na";
+ params["space"] = "dhcp6";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
- params["data"] = "ABCDEF01 02030405 06070809";
+ params["data"] = "12345, 6789, 1516";
+ params["csv-format"] = "True";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
@@ -750,11 +1343,11 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -779,9 +1372,9 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
// has been set correctly.
- EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
- EXPECT_EQ(0x02030405, optionIA->getT1());
- EXPECT_EQ(0x06070809, optionIA->getT2());
+ EXPECT_EQ(12345, optionIA->getIAID());
+ EXPECT_EQ(6789, optionIA->getT1());
+ EXPECT_EQ(1516, optionIA->getT2());
}
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 335fb7b..0ed4e85 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -19,15 +19,17 @@
#include <dhcp/dhcp6.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int_array.h>
+#include <dhcp/option_int_array.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
@@ -57,8 +59,11 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::sanityCheck;
};
class Dhcpv6SrvTest : public ::testing::Test {
@@ -68,8 +73,9 @@ public:
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
2000, 3000, 4000));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
+ CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet_);
}
@@ -139,6 +145,62 @@ public:
return (addr);
}
+ // Checks that server rejected IA_NA, i.e. that it has no addresses and
+ // that expected status code really appears there. In some limited cases
+ // (reply to RELEASE) it may be used to verify positive case, where
+ // IA_NA response is expected to not include address.
+ //
+ // Status code indicates type of error encountered (in theory it can also
+ // indicate success, but servers typically don't send success status
+ // as this is the default result and it saves bandwidth)
+ void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
+ uint16_t expected_status_code) {
+ // Make sure there is no address assigned.
+ EXPECT_FALSE(ia->getOption(D6O_IAADDR));
+
+ // T1, T2 should be zeroed
+ EXPECT_EQ(0, ia->getT1());
+ EXPECT_EQ(0, ia->getT2());
+
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status_code),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
+
+ void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -308,25 +370,27 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"pool\": [ \"2001:db8:1::/64\" ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"option-data\": [ {"
- " \"name\": \"OPTION_DNS_SERVERS\","
+ " \"name\": \"dns-servers\","
+ " \"space\": \"dhcp6\","
" \"code\": 23,"
- " \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
- "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+ " \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
+ " \"csv-format\": True"
" },"
" {"
- " \"name\": \"OPTION_FOO\","
- " \"code\": 1000,"
- " \"data\": \"1234\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+ NakedDhcpv6Srv srv(0);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
@@ -339,23 +403,23 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+ boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
- // We have not requested option with code 1000 so it should not
+ // We have not requested any options so they should not
// be included in the response.
- ASSERT_FALSE(adv->getOption(1000));
+ ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
- // Let's now request option with code 1000.
- // We expect that server will include this option in its reply.
- boost::shared_ptr<Option6IntArray<uint16_t> >
- option_oro(new Option6IntArray<uint16_t>(D6O_ORO));
+ // Let's now request some options. We expect that the server
+ // will include them in its response.
+ boost::shared_ptr<OptionIntArray<uint16_t> >
+ option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
// Create vector with two option codes.
std::vector<uint16_t> codes(2);
- codes[0] = 1000;
+ codes[0] = D6O_SUBSCRIBER_ID;
codes[1] = D6O_NAME_SERVERS;
// Pass this code to option.
option_oro->setValues(codes);
@@ -363,7 +427,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(option_oro);
// Need to process SOLICIT again after requesting new option.
- adv = srv->processSolicit(sol);
+ adv = srv.processSolicit(sol);
ASSERT_TRUE(adv);
OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -380,7 +444,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// There is a dummy option with code 1000 we requested from a server.
// Expect that this option is in server's response.
- tmp = adv->getOption(1000);
+ tmp = adv->getOption(D6O_SUBSCRIBER_ID);
ASSERT_TRUE(tmp);
// Check that the option contains valid data (from configuration).
@@ -414,8 +478,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -424,7 +487,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -437,7 +500,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -457,8 +520,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -475,7 +537,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -491,7 +553,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -511,8 +573,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -527,7 +588,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -541,20 +602,22 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
// This test checks that the server is offering different addresses to different
// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
-// and address will be assigned. Had the pool was very small and contained only
+// an address will be assigned. Had the pool was very small and contained only
// 2 addresses, the third client would get the same advertise as the first one
// and this is a correct behavior. It is REQUEST that will fail for the third
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManySolicits) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
@@ -578,9 +641,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
sol3->addOption(clientid3);
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processSolicit(sol1);
- Pkt6Ptr reply2 = srv->processSolicit(sol2);
- Pkt6Ptr reply3 = srv->processSolicit(sol3);
+ Pkt6Ptr reply1 = srv.processSolicit(sol1);
+ Pkt6Ptr reply2 = srv.processSolicit(sol2);
+ Pkt6Ptr reply3 = srv.processSolicit(sol3);
// check if we get response at all
checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
@@ -601,9 +664,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -617,7 +680,6 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
cout << "Offered address to client3=" << addr3->getAddress().toText() << endl;
}
-
// This test verifies that incoming REQUEST can be handled properly, that a
// REPLY is generated, that the response has an address and that address
// really belongs to the configured pool.
@@ -634,8 +696,7 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, RequestBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a REQUEST
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
@@ -651,8 +712,11 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
OptionPtr clientid = generateClientId();
req->addOption(clientid);
+ // server-id is mandatory in REQUEST
+ req->addOption(srv.getServerID());
+
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRequest(req);
+ Pkt6Ptr reply = srv.processRequest(req);
// check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -668,13 +732,13 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// check that the lease is really in the database
Lease6Ptr l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr);
EXPECT_TRUE(l);
- LeaseMgrFactory::instance().deleteLease6(addr->getAddress());
+ LeaseMgrFactory::instance().deleteLease(addr->getAddress());
}
// This test checks that the server is offering different addresses to different
@@ -685,8 +749,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManyRequests) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
@@ -709,10 +772,15 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
req2->addOption(clientid2);
req3->addOption(clientid3);
+ // server-id is mandatory in REQUEST
+ req1->addOption(srv.getServerID());
+ req2->addOption(srv.getServerID());
+ req3->addOption(srv.getServerID());
+
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processRequest(req1);
- Pkt6Ptr reply2 = srv->processRequest(req2);
- Pkt6Ptr reply3 = srv->processRequest(req3);
+ Pkt6Ptr reply1 = srv.processRequest(req1);
+ Pkt6Ptr reply2 = srv.processRequest(req2);
+ Pkt6Ptr reply3 = srv.processRequest(req3);
// check if we get response at all
checkResponse(reply1, DHCPV6_REPLY, 1234);
@@ -733,9 +801,9 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -749,87 +817,600 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
cout << "Assigned address to client3=" << addr3->getAddress().toText() << endl;
}
+// This test verifies that incoming (positive) RENEW can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv6SrvTest, RenewBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
-TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
- // Check all possible packet types
- for (int itype = 0; itype < 256; ++itype) {
- uint8_t type = itype;
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set and not using
+ // previous (500, 501, etc.) values
+ EXPECT_NE(l->t1_, subnet_->getT1());
+ EXPECT_NE(l->t2_, subnet_->getT2());
+ EXPECT_NE(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_NE(l->valid_lft_, subnet_->getValid());
+ EXPECT_NE(l->cltt_, time(NULL));
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
- switch (type) {
- case DHCPV6_CONFIRM:
- EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
- case DHCPV6_DECLINE:
- EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
- case DHCPV6_INFORMATION_REQUEST:
- EXPECT_STREQ("INFORMATION_REQUEST",
- Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
- case DHCPV6_REBIND:
- EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
- case DHCPV6_RELEASE:
- EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
- case DHCPV6_RENEW:
- EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check that IA_NA was returned and that there's an address included
+ boost::shared_ptr<Option6IAAddr> addr_opt = checkIA_NA(reply, 234, subnet_->getT1(),
+ subnet_->getT2());
- case DHCPV6_REQUEST:
- EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check that we've got the address we requested
+ checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
- case DHCPV6_SOLICIT:
- EXPECT_STREQ("SOLICIT", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
- default:
- EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
- }
- }
+ // Check that the lease is really in the database
+ l = checkLease(duid_, reply->getOption(D6O_IA_NA), addr_opt);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->preferred_lft_, subnet_->getPreferred());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr_opt->getAddress()));
+}
+
+// This test verifies that incoming (invalid) RENEW can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, RenewReject) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RENEW
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr renewed_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(renewed_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRenew(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+
+ // Check that there is no lease added
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 123; // Let's use it as an indicator that the lease
+ // was NOT updated.
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRenew(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+
+ // There is a iaid mis-match, so server should respond that there is
+ // no such address to renew.
+
+ // CASE 3: Lease belongs to a client with different client-id
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRenew(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+
+ lease = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(lease);
+ // Verify that the lease was not updated.
+ EXPECT_EQ(123, lease->cltt_);
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ checkIA_NAStatusCode(ia, STATUS_Success);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
+ EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: No lease known to server");
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Finally, let's cleanup the database
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
// This test verifies if the status code option is generated properly.
TEST_F(Dhcpv6SrvTest, StatusCode) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// a dummy content for client-id
- uint8_t expected[] = {0x0, 0x3, 0x41, 0x42, 0x43, 0x44, 0x45};
- OptionBuffer exp(expected, expected + sizeof(expected));
+ uint8_t expected[] = {
+ 0x0, 0xD, // option code = 13
+ 0x0, 0x7, // option length = 7
+ 0x0, 0x3, // status code = 3
+ 0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
+ };
+ // Create the option.
+ OptionPtr status = srv.createStatusCode(3, "ABCDE");
+ // Allocate an output buffer. We will store the option
+ // in wire format here.
+ OutputBuffer buf(sizeof(expected));
+ // Prepare the wire format.
+ ASSERT_NO_THROW(status->pack(buf));
+ // Check that the option buffer has valid length (option header + data).
+ ASSERT_EQ(sizeof(expected), buf.getLength());
+ // Verify the contents of the option.
+ EXPECT_EQ(0, memcmp(expected, buf.getData(), sizeof(expected)));
+}
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv6SrvTest, sanityCheck) {
+ NakedDhcpv6Srv srv(0);
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+
+ // Set link-local sender address, so appropriate subnet can be
+ // selected for this packet.
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ // client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+
+ // empty packet, no client-id, no server-id
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN),
+ RFCViolation);
+
+ // This doesn't make much sense, but let's check it for completeness
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::FORBIDDEN, Dhcpv6Srv::FORBIDDEN));
+
+ OptionPtr clientid = generateClientId();
+ pkt->addOption(clientid);
+
+ // client-id is mandatory, server-id is forbidden (as in SOLICIT or REBIND)
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
+
+ pkt->addOption(srv.getServerID());
+
+ // both client-id and server-id are mandatory (as in REQUEST, RENEW, RELEASE, DECLINE)
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
+
+ // sane section ends here, let's do some negative tests as well
+
+ pkt->addOption(clientid);
+ pkt->addOption(clientid);
+
+ // with more than one client-id it should throw, no matter what
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+
+ pkt->delOption(D6O_CLIENTID);
+ pkt->delOption(D6O_CLIENTID);
- OptionPtr status = srv->createStatusCode(3, "ABCDE");
+ // again we have only one client-id
- EXPECT_TRUE(status->getData() == exp);
+ // let's try different type of insanity - several server-ids
+ pkt->addOption(srv.getServerID());
+ pkt->addOption(srv.getServerID());
+
+ // with more than one server-id it should throw, no matter what
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ RFCViolation);
}
-// This test verifies if the selectSubnet() method works as expected.
-TEST_F(Dhcpv6SrvTest, SelectSubnet) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ // CASE 1: We have only one subnet defined and we received local traffic.
+ // The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
- // check that the packets originating from local addresses can be
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and we received relayed traffic.
+ // We should NOT select it.
+
+ // Identical steps as in case 1, but repeated for clarity
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+ pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have three subnets defined and we received local traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 5: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
- // packets originating from subnet A will select subnet A
- pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+ subnet1->setIface("eth0");
+ subnet3->setIface("wifi1");
- // packets from a subnet that is not supported will not get
- // a subnet
- pkt->setRemoteAddr(IOAddress("3000::faf"));
- EXPECT_FALSE(srv->selectSubnet(pkt));
+ // CASE 1: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth0. The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setIface("eth0");
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ pkt->setIface("eth1");
+
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+ // one over wifi1.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ pkt->setIface("eth0");
+ EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
+
+ pkt->setIface("eth3"); // no such interface
+ EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
+
+ pkt->setIface("wifi1");
+ EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
- /// @todo: expand this test once support for relays is implemented
}
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp6/tests/dhcp6_test.py b/src/bin/dhcp6/tests/dhcp6_test.py
index f3099b9..1870392 100644
--- a/src/bin/dhcp6/tests/dhcp6_test.py
+++ b/src/bin/dhcp6/tests/dhcp6_test.py
@@ -45,6 +45,25 @@ class TestDhcpv6Daemon(unittest.TestCase):
def tearDown(self):
pass
+ def readPipe(self, pipe_fd):
+ """
+ Reads bytes from a pipe and returns a character string. If nothing is
+ read, or if there is an error, an empty string is returned.
+
+ pipe_fd - Pipe file descriptor to read
+ """
+ try:
+ data = os.read(pipe_fd, 16384)
+ # Make sure we have a string
+ if (data is None):
+ data = ""
+ else:
+ data = str(data)
+ except OSError:
+ data = ""
+
+ return data
+
def runCommand(self, params, wait=1):
"""
This method runs a command and returns a tuple: (returncode, stdout, stderr)
@@ -89,45 +108,48 @@ class TestDhcpv6Daemon(unittest.TestCase):
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
- # There's potential problem if b10-dhcp4 prints out more
- # than 16k of text
- try:
- output = os.read(self.stdout_pipes[0], 16384)
- except OSError:
- print("No data available from stdout")
- output = ""
-
- # read can return None. Make sure we have a string
- if (output is None):
- output = ""
-
- try:
- error = os.read(self.stderr_pipes[0], 16384)
- except OSError:
- print("No data available on stderr")
- error = ""
-
- # read can return None. Make sure we have a string
- if (error is None):
- error = ""
-
- try:
- if (not pi.process.poll()):
- # let's be nice at first...
+ # As we don't know how long the subprocess will take to start and
+ # produce output, we'll loop and sleep for 250 ms between each
+ # iteration. To avoid an infinite loop, we'll loop for a maximum
+ # of five seconds: that should be enough.
+ for count in range(20):
+ # Read something from stderr and stdout (these reads don't block).
+ output = self.readPipe(self.stdout_pipes[0])
+ error = self.readPipe(self.stderr_pipes[0])
+
+ # If the process has already exited, or if it has output something,
+ # quit the loop now.
+ if pi.process.poll() is not None or len(error) > 0 or len(output) > 0:
+ break
+
+ # Process still running, try again in 250 ms.
+ time.sleep(0.25)
+
+ # Exited loop, kill the process if it is still running
+ if pi.process.poll() is None:
+ try:
pi.process.terminate()
- except OSError:
- print("Ignoring failed kill attempt. Process is dead already.")
+ except OSError:
+ print("Ignoring failed kill attempt. Process is dead already.")
# call this to get returncode, process should be dead by now
rc = pi.process.wait()
# Clean up our stdout/stderr munging.
os.dup2(self.stdout_old, sys.stdout.fileno())
+ os.close(self.stdout_old)
os.close(self.stdout_pipes[0])
os.dup2(self.stderr_old, sys.stderr.fileno())
+ os.close(self.stderr_old)
os.close(self.stderr_pipes[0])
+ # Free up resources (file descriptors) from the ProcessInfo object
+ # TODO: For some reason, this gives an error if the process has ended,
+ # although it does cause all descriptors still allocated to the
+ # object to be freed.
+ pi = None
+
print ("Process finished, return code=%d, stdout=%d bytes, stderr=%d bytes"
% (rc, len(output), len(error)) )
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 59f0bb5..41e280a 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,4 +1,5 @@
/b10-loadzone
/b10-loadzone.py
+/loadzone.py
/run_loadzone.sh
/b10-loadzone.8
diff --git a/src/bin/loadzone/Makefile.am b/src/bin/loadzone/Makefile.am
index 790f757..13b1501 100644
--- a/src/bin/loadzone/Makefile.am
+++ b/src/bin/loadzone/Makefile.am
@@ -1,12 +1,17 @@
-SUBDIRS = . tests/correct tests/error
+SUBDIRS = . tests
bin_SCRIPTS = b10-loadzone
noinst_SCRIPTS = run_loadzone.sh
-CLEANFILES = b10-loadzone
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/loadzone_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+
+CLEANFILES = b10-loadzone loadzone.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/loadzone_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/loadzone_messages.pyc
man_MANS = b10-loadzone.8
DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) b10-loadzone.xml
+EXTRA_DIST = $(man_MANS) b10-loadzone.xml loadzone_messages.mes
if GENERATE_DOCS
@@ -21,10 +26,13 @@ $(man_MANS):
endif
-b10-loadzone: b10-loadzone.py
- $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" \
- -e "s|@@LOCALSTATEDIR@@|$(localstatedir)|" \
- -e "s|@@LIBEXECDIR@@|$(pkglibexecdir)|" b10-loadzone.py >$@
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/loadzone_messages.py : loadzone_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/loadzone_messages.mes
+
+b10-loadzone: loadzone.py $(PYTHON_LOGMSGPKG_DIR)/work/loadzone_messages.py
+ $(SED) -e "s|@@PYTHONPATH@@|@pyexecdir@|" loadzone.py >$@
chmod a+x $@
EXTRA_DIST += tests/normal/README
@@ -48,6 +56,7 @@ EXTRA_DIST += tests/normal/sql1.example.com.signed
EXTRA_DIST += tests/normal/sql2.example.com
EXTRA_DIST += tests/normal/sql2.example.com.signed
-pytest:
- $(SHELL) tests/correct/correct_test.sh
- $(SHELL) tests/error/error_test.sh
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/bin/loadzone/TODO b/src/bin/loadzone/TODO
index d8d5f24..a33385d 100644
--- a/src/bin/loadzone/TODO
+++ b/src/bin/loadzone/TODO
@@ -1,16 +1,3 @@
-Support optional origin in $INCLUDE:
-$INCLUDE filename origin
-
-Support optional comment in $INCLUDE:
-$INCLUDE filename origin comment
-
-Support optional comment in $TTL (RFC 2308):
-$TTL number comment
-
-Do not assume "." is origin if origin is not set and sees a @ or
-a label without a ".". It should probably fail. (Don't assume a
-mistake means it is a root level label.)
-
Add verbose option to show what it is adding, not necessarily
in master file format, but in the context of the data source.
diff --git a/src/bin/loadzone/b10-loadzone.py.in b/src/bin/loadzone/b10-loadzone.py.in
deleted file mode 100644
index 83654f5..0000000
--- a/src/bin/loadzone/b10-loadzone.py.in
+++ /dev/null
@@ -1,94 +0,0 @@
-#!@PYTHON@
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import sys; sys.path.append ('@@PYTHONPATH@@')
-import re, getopt
-import isc.datasrc
-import isc.util.process
-from isc.datasrc.master import MasterFile
-import time
-import os
-
-isc.util.process.rename()
-
-#########################################################################
-# usage: print usage note and exit
-#########################################################################
-def usage():
- print("Usage: %s [-d <database>] [-o <origin>] <file>" % sys.argv[0], \
- file=sys.stderr)
- exit(1)
-
-#########################################################################
-# main
-#########################################################################
-def main():
- try:
- opts, args = getopt.getopt(sys.argv[1:], "d:o:h", \
- ["dbfile", "origin", "help"])
- except getopt.GetoptError as e:
- print(str(e))
- usage()
- exit(2)
-
- dbfile = '@@LOCALSTATEDIR@@/@PACKAGE@/zone.sqlite3'
- initial_origin = ''
- for o, a in opts:
- if o in ("-d", "--dbfile"):
- dbfile = a
- elif o in ("-o", "--origin"):
- if a[-1] != '.':
- a += '.'
- initial_origin = a
- elif o in ("-h", "--help"):
- usage()
- else:
- assert False, "unhandled option"
-
- if len(args) != 1:
- usage()
- zonefile = args[0]
- verbose = os.isatty(sys.stdout.fileno())
- try:
- master = MasterFile(zonefile, initial_origin, verbose)
- except Exception as e:
- sys.stderr.write("Error reading zone file: %s\n" % str(e))
- exit(1)
-
- try:
- zone = master.zonename()
- if verbose:
- sys.stdout.write("Using SQLite3 database file %s\n" % dbfile)
- sys.stdout.write("Zone name is %s\n" % zone)
- sys.stdout.write("Loading file \"%s\"\n" % zonefile)
- except Exception as e:
- sys.stdout.write("\n")
- sys.stderr.write("Error reading zone file: %s\n" % str(e))
- exit(1)
-
- try:
- isc.datasrc.sqlite3_ds.load(dbfile, zone, master.zonedata)
- if verbose:
- master.closeverbose()
- sys.stdout.write("\nDone.\n")
- except Exception as e:
- sys.stdout.write("\n")
- sys.stderr.write("Error loading database: %s\n"% str(e))
- exit(1)
-
-if __name__ == "__main__":
- main()
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index 8c41e54..aa14053 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-
- Permission to use, copy, modify, and/or distribute this software for any
- purpose with or without fee is hereby granted, provided that the above
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>March 26, 2012</date>
+ <date>December 15, 2012</date>
</refentryinfo>
<refmeta>
@@ -36,7 +36,7 @@
<docinfo>
<copyright>
- <year>2010</year>
+ <year>2012</year>
<holder>Internet Systems Consortium, Inc. ("ISC")</holder>
</copyright>
</docinfo>
@@ -44,9 +44,13 @@
<refsynopsisdiv>
<cmdsynopsis>
<command>b10-loadzone</command>
- <arg><option>-d <replaceable class="parameter">database</replaceable></option></arg>
- <arg><option>-o <replaceable class="parameter">origin</replaceable></option></arg>
- <arg choice="req">filename</arg>
+ <arg><option>-c <replaceable class="parameter">datasrc_config</replaceable></option></arg>
+ <arg><option>-d <replaceable class="parameter">debug_level</replaceable></option></arg>
+ <arg><option>-i <replaceable class="parameter">report_interval</replaceable></option></arg>
+ <arg><option>-t <replaceable class="parameter">datasrc_type</replaceable></option></arg>
+ <arg><option>-C <replaceable class="parameter">zone_class</replaceable></option></arg>
+ <arg choice="req">zone name</arg>
+ <arg choice="req">zone file</arg>
</cmdsynopsis>
</refsynopsisdiv>
@@ -63,11 +67,9 @@
<para>
Some control entries (aka directives) are supported.
- $ORIGIN is followed by a domain name, and sets the the origin
+ $ORIGIN is followed by a domain name, and sets the origin
that will be used for relative domain names in subsequent records.
$INCLUDE is followed by a filename to load.
-<!-- TODO: and optionally a
- domain name used to set the relative domain name origin. -->
The previous origin is restored after the file is included.
<!-- the current domain name is also restored -->
$TTL is followed by a time-to-live value which is used
@@ -75,11 +77,31 @@
</para>
<para>
+ If the specified zone does not exist in the specified data
+ source, <command>b10-loadzone</command> will first create a
+ new empty zone in the data source, then fill it with the RRs
+ given in the specified master zone file. In this case, if
+ loading fails for some reason, the creation of the new zone
+ is also canceled.
+ <note><simpara>
+ Due to an implementation limitation, the current version
+ does not make the zone creation and subsequent loading an
+ atomic operation; an empty zone will be visible and used by
+ other application (e.g., the <command>b10-auth</command>
+ authoritative server) while loading. If this is an issue,
+ make sure the initial loading of a new zone is done before
+ starting other BIND 10 applications.
+ </simpara></note>
+ </para>
+
+ <para>
When re-loading an existing zone, the prior version is completely
removed. While the new version of the zone is being loaded, the old
version remains accessible to queries. After the new version is
completely loaded, the old version is swapped out and replaced
- with the new one in a single operation.
+ with the new one in a single operation. If loading fails for
+ some reason, the loaded RRs will be effectively deleted, and the
+ old version will still remain accessible for other applications.
</para>
</refsect1>
@@ -88,21 +110,82 @@
<title>ARGUMENTS</title>
<variablelist>
+ <varlistentry>
+ <term>-c <replaceable class="parameter">datasrc_config</replaceable></term>
+ <listitem><para>
+ Specifies configuration of the data source in the JSON
+ format. The configuration contents depend on the type of
+ the data source, and that's the same as what would be
+ specified for the BIND 10 servers (see the data source
+ configuration section of the BIND 10 guide). For example,
+ for an SQLite3 data source, it would look like
+ '{"database_file": "path-to-sqlite3-db-file"}'.
+ <note>
+ <simpara>For SQLite3 data source with the default DB file,
+ this option can be omitted; in other cases including
+ for any other types of data sources when supported,
+ this option is currently mandatory in practice.
+ In a future version it will be possible to retrieve the
+ configuration from the BIND 10 server configuration (if
+ it exists).
+ </simpara></note>
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-d <replaceable class="parameter">debug_level</replaceable> </term>
+ <listitem><para>
+ Enable dumping debug level logging with the specified
+ level. By default, only log messages at the severity of
+ informational or higher levels will be produced.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-i <replaceable class="parameter">report_interval</replaceable></term>
+ <listitem><para>
+ Specifies the interval of status update by the number of RRs
+ loaded in the interval.
+ The <command>b10-loadzone</command> tool periodically
+ reports the progress of loading with the total number of
+ loaded RRs and elapsed time. This option specifies the
+ interval of the reports. If set to 0, status reports will
+ be suppressed. The default is 10,000.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-t <replaceable class="parameter">datasrc_type</replaceable></term>
+ <listitem><para>
+ Specifies the type of data source to store the zone.
+ Currently, only the "sqlite3" type is supported (which is
+ the default of this option), which means the SQLite3 data
+ source.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>-C <replaceable class="parameter">zone_class</replaceable></term>
+ <listitem><para>
+ Specifies the RR class of the zone.
+ Currently, only class IN is supported (which is the default
+ of this option) due to limitation of the underlying data
+ source implementation.
+ </para></listitem>
+ </varlistentry>
<varlistentry>
- <term>-d <replaceable class="parameter">database</replaceable> </term>
+ <term><replaceable class="parameter">zone name</replaceable></term>
<listitem><para>
- Defines the filename for the database.
- The default is
- <filename>/usr/local/var/bind10-devel/zone.sqlite3</filename>.
-<!-- TODO: fix filename -->
+ The name of the zone to create or update. This must be a valid DNS
+ domain name.
</para></listitem>
</varlistentry>
<varlistentry>
- <term>-o <replaceable class="parameter">origin</replaceable></term>
+ <term><replaceable class="parameter">zone file</replaceable></term>
<listitem><para>
- Defines the default origin for the zone file records.
+ A path to the master zone file to be loaded.
</para></listitem>
</varlistentry>
@@ -131,8 +214,17 @@
<refsect1>
<title>AUTHORS</title>
<para>
- The <command>b10-loadzone</command> tool was initial written
- by Evan Hunt of ISC.
+ A prior version of the <command>b10-loadzone</command> tool was
+ written by Evan Hunt of ISC.
+ The new version that this manual refers to was rewritten from
+ the scratch by the BIND 10 development team in around December 2012.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>BUGS</title>
+ <para>
+ There are some issues noted in the DESCRIPTION section.
</para>
</refsect1>
</refentry><!--
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
new file mode 100755
index 0000000..3ed3b7d
--- /dev/null
+++ b/src/bin/loadzone/loadzone.py.in
@@ -0,0 +1,296 @@
+#!@PYTHON@
+
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import sys
+sys.path.append('@@PYTHONPATH@@')
+import time
+import signal
+from optparse import OptionParser
+from isc.dns import *
+from isc.datasrc import *
+import isc.util.process
+import isc.log
+from isc.log_messages.loadzone_messages import *
+
+isc.util.process.rename()
+
+# These are needed for logger settings
+import bind10_config
+import json
+from isc.config import module_spec_from_file
+from isc.config.ccsession import path_search
+
+isc.log.init("b10-loadzone")
+logger = isc.log.Logger("loadzone")
+
+# The default value for the interval of progress report in terms of the
+# number of RRs loaded in that interval. Arbitrary choice, but intended to
+# be reasonably small to handle emergency exit.
+LOAD_INTERVAL_DEFAULT = 10000
+
+class BadArgument(Exception):
+ '''An exception indicating an error in command line argument.
+
+ '''
+ pass
+
+class LoadFailure(Exception):
+ '''An exception indicating failure in loading operation.
+
+ '''
+ pass
+
+def set_cmd_options(parser):
+ '''Helper function to set command-line options.
+
+ '''
+ parser.add_option("-c", "--datasrc-conf", dest="conf", action="store",
+ help="""configuration of datasrc to load the zone in.
+Example: '{"database_file": "/path/to/dbfile/db.sqlite3"}'""",
+ metavar='CONFIG')
+ parser.add_option("-d", "--debug", dest="debug_level",
+ type='int', action="store", default=None,
+ help="enable debug logs with the specified level [0-99]")
+ parser.add_option("-i", "--report-interval", dest="report_interval",
+ type='int', action="store",
+ default=LOAD_INTERVAL_DEFAULT,
+ help="""report logs progress per specified number of RRs
+(specify 0 to suppress report) [default: %default]""")
+ parser.add_option("-t", "--datasrc-type", dest="datasrc_type",
+ action="store", default='sqlite3',
+ help="""type of data source (e.g., 'sqlite3')\n
+[default: %default]""")
+ parser.add_option("-C", "--class", dest="zone_class", action="store",
+ default='IN',
+ help="""RR class of the zone; currently must be 'IN'
+[default: %default]""")
+
+class LoadZoneRunner:
+ '''Main logic for the loadzone.
+
+ This is implemented as a class mainly for the convenience of tests.
+
+ '''
+ def __init__(self, command_args):
+ self.__command_args = command_args
+ self.__loaded_rrs = 0
+ self.__interrupted = False # will be set to True on receiving signal
+
+ # system-wide log configuration. We need to configure logging this
+ # way so that the logging policy applies to underlying libraries, too.
+ self.__log_spec = json.dumps(isc.config.module_spec_from_file(
+ path_search('logging.spec', bind10_config.PLUGIN_PATHS)).
+ get_full_spec())
+ # "severity" and "debuglevel" are the tunable parameters, which will
+ # be set in _config_log().
+ self.__log_conf_base = {"loggers":
+ [{"name": "*",
+ "output_options":
+ [{"output": "stderr",
+ "destination": "console"}]}]}
+
+ # These are essentially private, and defined as "protected" for the
+ # convenience of tests inspecting them
+ self._zone_class = None
+ self._zone_name = None
+ self._zone_file = None
+ self._datasrc_config = None
+ self._datasrc_type = None
+ self._log_severity = 'INFO'
+ self._log_debuglevel = 0
+ self._report_interval = LOAD_INTERVAL_DEFAULT
+
+ self._config_log()
+
+ def _config_log(self):
+ '''Configure logging policy.
+
+ This is essentially private, but defined as "protected" for tests.
+
+ '''
+ self.__log_conf_base['loggers'][0]['severity'] = self._log_severity
+ self.__log_conf_base['loggers'][0]['debuglevel'] = self._log_debuglevel
+ isc.log.log_config_update(json.dumps(self.__log_conf_base),
+ self.__log_spec)
+
+ def _parse_args(self):
+ '''Parse command line options and other arguments.
+
+ This is essentially private, but defined as "protected" for tests.
+
+ '''
+
+ usage_txt = \
+ 'usage: %prog [options] -c datasrc_config zonename zonefile'
+ parser = OptionParser(usage=usage_txt)
+ set_cmd_options(parser)
+ (options, args) = parser.parse_args(args=self.__command_args)
+
+ # Configure logging policy as early as possible
+ if options.debug_level is not None:
+ self._log_severity = 'DEBUG'
+ # optparse performs type check
+ self._log_debuglevel = int(options.debug_level)
+ if self._log_debuglevel < 0:
+ raise BadArgument(
+ 'Invalid debug level (must be non negative): %d' %
+ self._log_debuglevel)
+ self._config_log()
+
+ self._datasrc_type = options.datasrc_type
+ self._datasrc_config = options.conf
+ if options.conf is None:
+ self._datasrc_config = self._get_datasrc_config(self._datasrc_type)
+ try:
+ self._zone_class = RRClass(options.zone_class)
+ except isc.dns.InvalidRRClass as ex:
+ raise BadArgument('Invalid zone class: ' + str(ex))
+ if self._zone_class != RRClass.IN():
+ raise BadArgument("RR class is not supported: " +
+ str(self._zone_class))
+
+ self._report_interval = int(options.report_interval)
+ if self._report_interval < 0:
+ raise BadArgument(
+ 'Invalid report interval (must be non negative): %d' %
+ self._report_interval)
+
+ if len(args) != 2:
+ raise BadArgument('Unexpected number of arguments: %d (must be 2)'
+ % (len(args)))
+ try:
+ self._zone_name = Name(args[0])
+ except Exception as ex: # too broad, but there's no better granurality
+ raise BadArgument("Invalid zone name '" + args[0] + "': " +
+ str(ex))
+ self._zone_file = args[1]
+
+ def _get_datasrc_config(self, datasrc_type):
+ ''''Return the default data source configuration of given type.
+
+ Right now, it only supports SQLite3, and hardcodes the syntax
+ of the default configuration. It's a kind of workaround to balance
+ convenience of users and minimizing hardcoding of data source
+ specific logic in the entire tool. In future this should be
+ more sophisticated.
+
+ This is essentially a private helper method for _parse_arg(),
+ but defined as "protected" so tests can use it directly.
+
+ '''
+ if datasrc_type != 'sqlite3':
+ raise BadArgument('default config is not available for ' +
+ datasrc_type)
+
+ default_db_file = bind10_config.DATA_PATH + '/zone.sqlite3'
+ logger.info(LOADZONE_SQLITE3_USING_DEFAULT_CONFIG, default_db_file)
+ return '{"database_file": "' + default_db_file + '"}'
+
+ def _report_progress(self, loaded_rrs):
+ '''Dump the current progress report to stdout.
+
+ This is essentially private, but defined as "protected" for tests.
+
+ '''
+ elapsed = time.time() - self.__start_time
+ sys.stdout.write("\r" + (80 * " "))
+ sys.stdout.write("\r%d RRs loaded in %.2f seconds" %
+ (loaded_rrs, elapsed))
+
+ def _do_load(self):
+ '''Main part of the load logic.
+
+ This is essentially private, but defined as "protected" for tests.
+
+ '''
+ created = False
+ try:
+ datasrc_client = DataSourceClient(self._datasrc_type,
+ self._datasrc_config)
+ created = datasrc_client.create_zone(self._zone_name)
+ if created:
+ logger.info(LOADZONE_ZONE_CREATED, self._zone_name,
+ self._zone_class)
+ else:
+ logger.info(LOADZONE_ZONE_UPDATING, self._zone_name,
+ self._zone_class)
+ loader = ZoneLoader(datasrc_client, self._zone_name,
+ self._zone_file)
+ self.__start_time = time.time()
+ if self._report_interval > 0:
+ limit = self._report_interval
+ else:
+ # Even if progress report is suppressed, we still load
+ # incrementally so we won't delay catching signals too long.
+ limit = LOAD_INTERVAL_DEFAULT
+ while (not self.__interrupted and
+ not loader.load_incremental(limit)):
+ self.__loaded_rrs += self._report_interval
+ if self._report_interval > 0:
+ self._report_progress(self.__loaded_rrs)
+ if self.__interrupted:
+ raise LoadFailure('loading interrupted by signal')
+
+ # On successful completion, add final '\n' to the progress
+ # report output (on failure don't bother to make it prettier).
+ if (self._report_interval > 0 and
+ self.__loaded_rrs >= self._report_interval):
+ sys.stdout.write('\n')
+ except Exception as ex:
+ # release any remaining lock held in the loader
+ loader = None
+ if created:
+ datasrc_client.delete_zone(self._zone_name)
+ logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
+ self._zone_class)
+ raise LoadFailure(str(ex))
+
+ def _set_signal_handlers(self):
+ signal.signal(signal.SIGINT, self._interrupt_handler)
+ signal.signal(signal.SIGTERM, self._interrupt_handler)
+
+ def _interrupt_handler(self, signal, frame):
+ self.__interrupted = True
+
+ def run(self):
+ '''Top-level method, simply calling other helpers'''
+
+ try:
+ self._set_signal_handlers()
+ self._parse_args()
+ self._do_load()
+ total_elapsed_txt = "%.2f" % (time.time() - self.__start_time)
+ logger.info(LOADZONE_DONE, self.__loaded_rrs, self._zone_name,
+ self._zone_class, total_elapsed_txt)
+ return 0
+ except BadArgument as ex:
+ logger.error(LOADZONE_ARGUMENT_ERROR, ex)
+ except LoadFailure as ex:
+ logger.error(LOADZONE_LOAD_ERROR, self._zone_name,
+ self._zone_class, ex)
+ except Exception as ex:
+ logger.error(LOADZONE_UNEXPECTED_FAILURE, ex)
+ return 1
+
+if '__main__' == __name__:
+ runner = LoadZoneRunner(sys.argv[1:])
+ ret = runner.run()
+ sys.exit(ret)
+
+## Local Variables:
+## mode: python
+## End:
diff --git a/src/bin/loadzone/loadzone_messages.mes b/src/bin/loadzone/loadzone_messages.mes
new file mode 100644
index 0000000..ca241b3
--- /dev/null
+++ b/src/bin/loadzone/loadzone_messages.mes
@@ -0,0 +1,73 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% LOADZONE_ARGUMENT_ERROR Error in command line arguments: %1
+Some semantics error in command line arguments or options to b10-loadzone
+is detected. b10-loadzone does effectively nothing and immediately
+terminates.
+
+% LOADZONE_CANCEL_CREATE_ZONE Creation of new zone %1/%2 was canceled
+b10-loadzone has created a new zone in the data source (see
+LOADZONE_ZONE_CREATED), but the loading operation has subsequently
+failed. The newly created zone has been removed from the data source,
+so that the data source will go back to the original state.
+
+% LOADZONE_DONE Loaded (at least) %1 RRs into zone %2/%3 in %4 seconds
+b10-loadzone has successfully loaded the specified zone. If there was
+an old version of the zone in the data source, it is now deleted.
+It also prints (a lower bound of) the number of RRs that have been loaded
+and the time spent for the loading. Due to a limitation of the
+current implementation of the underlying library however, it cannot show the
+exact number of the loaded RRs; it's counted for every N-th RR where N
+is the value of the -i command line option. So, for smaller zones that
+don't even contain N RRs, the reported value will be 0. This will be
+improved in a future version.
+
+% LOADZONE_LOAD_ERROR Failed to load zone %1/%2: %3
+Loading a zone by b10-loadzone fails for some reason in the middle of
+the loading. This is most likely due to an error in the specified
+arguments to b10-loadzone (such as non-existent zone file) or an error
+in the zone file. When this happens, the RRs loaded so far are
+effectively deleted from the zone, and the old version (if exists)
+will still remain valid for operations.
+
+% LOADZONE_SQLITE3_USING_DEFAULT_CONFIG Using default configuration with SQLite3 DB file %1
+The SQLite3 data source is specified as the data source type without a
+data source configuration. b10-loadzone uses the default
+configuration with the default DB file for the BIND 10 system.
+
+% LOADZONE_UNEXPECTED_FAILURE Unexpected exception: %1
+b10-loadzone encounters an unexpected failure and terminates itself.
+This is generally a bug of b10-loadzone itself or the underlying
+data source library, so it's advisable to submit a bug report if
+this message is logged. The incomplete attempt of loading should
+have been cleanly canceled in this case, too.
+
+% LOADZONE_ZONE_CREATED Zone %1/%2 does not exist in the data source, newly created
+The specified zone to b10-loadzone to load does not exist in the
+specified data source. b10-loadzone has created a new empty zone
+in the data source.
+
+% LOADZONE_ZONE_UPDATING Started updating zone %1/%2 with removing old data (this can take a while)
+b10-loadzone started loading a new version of the zone as specified,
+beginning with removing the current contents of the zone (in a
+transaction, so the removal won't take effect until and unless the entire
+load is completed successfully). If the old version of the zone is large,
+this can take time, such as a few minutes or more, without any visible
+feedback. This is not a problem as long as the b10-loadzone process
+is working at a moderate load.
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index 43b7920..b3d61d3 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -18,7 +18,7 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
@@ -32,5 +32,13 @@ fi
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
+# For bind10_config
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+
+# For data source loadable modules
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
exec ${LOADZONE_PATH}/b10-loadzone "$@"
diff --git a/src/bin/loadzone/tests/Makefile.am b/src/bin/loadzone/tests/Makefile.am
new file mode 100644
index 0000000..8459f83
--- /dev/null
+++ b/src/bin/loadzone/tests/Makefile.am
@@ -0,0 +1,37 @@
+SUBDIRS = . correct
+
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = loadzone_test.py
+
+EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/example.org.zone
+EXTRA_DIST += testdata/broken-example.org.zone
+EXTRA_DIST += testdata/example-nosoa.org.zone
+EXTRA_DIST += testdata/example-nons.org.zone
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$(abs_top_builddir)/src/lib/acl/.libs:$$$(ENV_LIBRARY_PATH)
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+# We need to define B10_FROM_BUILD for datasrc loadable modules
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ TESTDATA_PATH=$(abs_top_srcdir)/src/lib/testutils/testdata \
+ LOCAL_TESTDATA_PATH=$(srcdir)/testdata \
+ TESTDATA_WRITE_PATH=$(builddir) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/loadzone:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/bin/loadzone/tests/correct/Makefile.am b/src/bin/loadzone/tests/correct/Makefile.am
index a3c67d4..7ed500d 100644
--- a/src/bin/loadzone/tests/correct/Makefile.am
+++ b/src/bin/loadzone/tests/correct/Makefile.am
@@ -26,5 +26,8 @@ endif
# TODO: maybe use TESTS?
# test using command-line arguments, so use check-local target instead of TESTS
check-local:
- echo Running test: correct_test.sh
+ echo Running test: correct_test.sh
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/loadzone:$(abs_top_builddir)/src/lib/dns/python/.libs \
$(LIBRARY_PATH_PLACEHOLDER) $(SHELL) $(abs_builddir)/correct_test.sh
diff --git a/src/bin/loadzone/tests/correct/correct_test.sh.in b/src/bin/loadzone/tests/correct/correct_test.sh.in
index e3f6a84..9b90d13 100755
--- a/src/bin/loadzone/tests/correct/correct_test.sh.in
+++ b/src/bin/loadzone/tests/correct/correct_test.sh.in
@@ -18,7 +18,7 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python:$PYTHONPATH
export PYTHONPATH
LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
@@ -28,28 +28,28 @@ TEST_OUTPUT_PATH=@abs_top_builddir@/src/bin/loadzone//tests/correct
status=0
echo "Loadzone include. from include.db file"
cd ${TEST_FILE_PATH}
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 include.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' include. include.db >> /dev/null
echo "loadzone ttl1. from ttl1.db file"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 ttl1.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' ttl1. ttl1.db >> /dev/null
echo "loadzone ttl2. from ttl2.db file"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 ttl2.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' ttl2. ttl2.db >> /dev/null
echo "loadzone mix1. from mix1.db"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 mix1.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' mix1. mix1.db >> /dev/null
echo "loadzone mix2. from mix2.db"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 mix2.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' mix2. mix2.db >> /dev/null
echo "loadzone ttlext. from ttlext.db"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 ttlext.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' ttlext. ttlext.db >> /dev/null
echo "loadzone example.com. from example.db"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 example.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' example.com. example.db >> /dev/null
echo "loadzone comment.example.com. from comment.db"
-${LOADZONE_PATH}/b10-loadzone -d ${TEST_OUTPUT_PATH}/zone.sqlite3 comment.db >> /dev/null
+${LOADZONE_PATH}/b10-loadzone -c '{"database_file": "'${TEST_OUTPUT_PATH}/zone.sqlite3'"}' comment.example.com. comment.db >> /dev/null
echo "I:test master file \$INCLUDE semantics"
echo "I:test master file BIND 8 compatibility TTL and \$TTL semantics"
diff --git a/src/bin/loadzone/tests/correct/example.db b/src/bin/loadzone/tests/correct/example.db
index fe012cf..38d1329 100644
--- a/src/bin/loadzone/tests/correct/example.db
+++ b/src/bin/loadzone/tests/correct/example.db
@@ -2,11 +2,17 @@
$ORIGIN example.com.
$TTL 60
@ IN SOA ns1.example.com. hostmaster.example.com. (1 43200 900 1814400 7200)
- IN 20 NS ns1
- NS ns2
+; these need #2390
+; IN 20 NS ns1
+; NS ns2
+ IN 20 NS ns1.example.com.
+ NS ns2.example.com.
ns1 IN 30 A 192.168.1.102
- 70 NS ns3
- IN NS ns4
+; these need #2390
+; 70 NS ns3
+; IN NS ns4
+ 70 NS ns3.example.com.
+ IN NS ns4.example.com.
10 IN MX 10 mail.example.com.
ns2 80 A 1.1.1.1
ns3 IN A 2.2.2.2
diff --git a/src/bin/loadzone/tests/correct/include.db b/src/bin/loadzone/tests/correct/include.db
index f60a240..53871bb 100644
--- a/src/bin/loadzone/tests/correct/include.db
+++ b/src/bin/loadzone/tests/correct/include.db
@@ -7,7 +7,9 @@ $TTL 300
1814400
3600
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.include.
ns A 127.0.0.1
diff --git a/src/bin/loadzone/tests/correct/known.test.out b/src/bin/loadzone/tests/correct/known.test.out
index eec692e..377158d 100644
--- a/src/bin/loadzone/tests/correct/known.test.out
+++ b/src/bin/loadzone/tests/correct/known.test.out
@@ -80,6 +80,6 @@ ns5.example.com. 90 IN A 4.4.4.4
comment.example.com. 60 IN SOA ns1.example.com. hostmaster.example.com. 1 43200 900 1814400 7200
comment.example.com. 60 IN NS ns1.example.com.
comment.example.com. 60 IN TXT "Simple text"
-comment.example.com. 60 IN TXT "; No comment"
+comment.example.com. 60 IN TXT "\; No comment"
comment.example.com. 60 IN TXT "Also no comment here"
-comment.example.com. 60 IN TXT "A combination ; see?"
+comment.example.com. 60 IN TXT "A combination \; see?"
diff --git a/src/bin/loadzone/tests/correct/mix1.db b/src/bin/loadzone/tests/correct/mix1.db
index a9d58a8..059fde7 100644
--- a/src/bin/loadzone/tests/correct/mix1.db
+++ b/src/bin/loadzone/tests/correct/mix1.db
@@ -6,7 +6,9 @@ $ORIGIN mix1.
1814400
3
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.mix1.
ns A 10.53.0.1
a TXT "soa minttl 3"
b 2 TXT "explicit ttl 2"
diff --git a/src/bin/loadzone/tests/correct/mix2.db b/src/bin/loadzone/tests/correct/mix2.db
index 2c8153d..e89c2af 100644
--- a/src/bin/loadzone/tests/correct/mix2.db
+++ b/src/bin/loadzone/tests/correct/mix2.db
@@ -6,7 +6,9 @@ $ORIGIN mix2.
1814400
3
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.mix2.
ns A 10.53.0.1
a TXT "inherited ttl 1"
$INCLUDE mix2sub1.txt
diff --git a/src/bin/loadzone/tests/correct/mix2sub2.txt b/src/bin/loadzone/tests/correct/mix2sub2.txt
index 96d53c1..7e4292a 100644
--- a/src/bin/loadzone/tests/correct/mix2sub2.txt
+++ b/src/bin/loadzone/tests/correct/mix2sub2.txt
@@ -1,3 +1,3 @@
-f TXT "default ttl 3"
+f TXT "default ttl 3"
$TTL 5
-g TXT "default ttl 5"
+g TXT "default ttl 5"
diff --git a/src/bin/loadzone/tests/correct/ttl1.db b/src/bin/loadzone/tests/correct/ttl1.db
index aa6e2bb..7f04ff8 100644
--- a/src/bin/loadzone/tests/correct/ttl1.db
+++ b/src/bin/loadzone/tests/correct/ttl1.db
@@ -6,7 +6,9 @@ $ORIGIN ttl1.
1814400
3
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.ttl1.
ns A 10.53.0.1
a TXT "soa minttl 3"
b 2 TXT "explicit ttl 2"
diff --git a/src/bin/loadzone/tests/correct/ttl2.db b/src/bin/loadzone/tests/correct/ttl2.db
index f7f6eee..b7df040 100644
--- a/src/bin/loadzone/tests/correct/ttl2.db
+++ b/src/bin/loadzone/tests/correct/ttl2.db
@@ -6,7 +6,9 @@ $ORIGIN ttl2.
1814400
3
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.ttl2.
ns A 10.53.0.1
a TXT "inherited ttl 1"
b 2 TXT "explicit ttl 2"
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8b96ea..844f452 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -6,7 +6,9 @@ $ORIGIN ttlext.
1814400
3
)
- NS ns
+; this needs #2390
+; NS ns
+ NS ns.ttlext.
ns A 10.53.0.1
a TXT "soa minttl 3"
b 2S TXT "explicit ttl 2"
diff --git a/src/bin/loadzone/tests/error/.gitignore b/src/bin/loadzone/tests/error/.gitignore
deleted file mode 100644
index 5d20adb..0000000
--- a/src/bin/loadzone/tests/error/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/error_test.sh
diff --git a/src/bin/loadzone/tests/error/Makefile.am b/src/bin/loadzone/tests/error/Makefile.am
deleted file mode 100644
index 03263b7..0000000
--- a/src/bin/loadzone/tests/error/Makefile.am
+++ /dev/null
@@ -1,28 +0,0 @@
-EXTRA_DIST = error.known
-EXTRA_DIST += formerr1.db
-EXTRA_DIST += formerr2.db
-EXTRA_DIST += formerr3.db
-EXTRA_DIST += formerr4.db
-EXTRA_DIST += formerr5.db
-EXTRA_DIST += include.txt
-EXTRA_DIST += keyerror1.db
-EXTRA_DIST += keyerror2.db
-EXTRA_DIST += keyerror3.db
-#EXTRA_DIST += nofilenane.db
-EXTRA_DIST += originerr1.db
-EXTRA_DIST += originerr2.db
-
-noinst_SCRIPTS = error_test.sh
-
-# If necessary (rare cases), explicitly specify paths to dynamic libraries
-# required by loadable python modules.
-LIBRARY_PATH_PLACEHOLDER =
-if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
-endif
-
-# TODO: use TESTS ?
-# test using command-line arguments, so use check-local target instead of TESTS
-check-local:
- echo Running test: error_test.sh
- $(LIBRARY_PATH_PLACEHOLDER) $(SHELL) $(abs_builddir)/error_test.sh
diff --git a/src/bin/loadzone/tests/error/error.known b/src/bin/loadzone/tests/error/error.known
deleted file mode 100644
index cdbbed2..0000000
--- a/src/bin/loadzone/tests/error/error.known
+++ /dev/null
@@ -1,11 +0,0 @@
-Error reading zone file: Cannot parse RR, No $ORIGIN: @ IN SOA ns hostmaster 1 3600 1800 1814400 3600
-Error reading zone file: $ORIGIN is not absolute in record: $ORIGIN com
-Error reading zone file: Cannot parse RR: $TL 300
-Error reading zone file: Cannot parse RR: $OIGIN com.
-Error loading database: Error while loading com.: Cannot parse RR: $INLUDE file.txt
-Error loading database: Error while loading com.: Invalid $include format
-Error loading database: Error while loading com.: Cannot parse RR, No $ORIGIN: include.txt sub
-Error reading zone file: Invalid TTL: ""
-Error reading zone file: Invalid TTL: "M"
-Error loading database: Error while loading com.: Cannot parse RR: b "no type error!"
-Error reading zone file: Could not open bogusfile
diff --git a/src/bin/loadzone/tests/error/error_test.sh.in b/src/bin/loadzone/tests/error/error_test.sh.in
deleted file mode 100755
index 94c5edb..0000000
--- a/src/bin/loadzone/tests/error/error_test.sh.in
+++ /dev/null
@@ -1,82 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
-export PYTHON_EXEC
-
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python
-export PYTHONPATH
-
-LOADZONE_PATH=@abs_top_builddir@/src/bin/loadzone
-TEST_OUTPUT_PATH=@abs_top_builddir@/src/bin/loadzone/tests/error
-TEST_FILE_PATH=@abs_top_srcdir@/src/bin/loadzone/tests/error
-
-cd ${LOADZONE_PATH}/tests/error
-
-export LOADZONE_PATH
-status=0
-
-echo "PYTHON PATH: $PYTHONPATH"
-
-echo "Test no \$ORIGIN error in zone file"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/originerr1.db 1> /dev/null 2> error.out
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/originerr2.db 1> /dev/null 2>> error.out
-
-echo "Test: key word TTL spell error"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/keyerror1.db 1> /dev/null 2>> error.out
-
-echo "Test: key word ORIGIN spell error"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/keyerror2.db 1> /dev/null 2>> error.out
-
-echo "Test: key INCLUDE spell error"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/keyerror3.db 1> /dev/null 2>> error.out
-
-echo "Test: include formal error, miss filename"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/formerr1.db 1> /dev/null 2>>error.out
-
-echo "Test: include form error, domain is not absolute"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/formerr2.db 1> /dev/null 2>> error.out
-
-echo "Test: TTL form error, no ttl value"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/formerr3.db 1> /dev/null 2>> error.out
-
-echo "Test: TTL form error, ttl value error"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/formerr4.db 1> /dev/null 2>> error.out
-
-echo "Test: rr form error, no type"
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 ${TEST_FILE_PATH}/formerr5.db 1> /dev/null 2>> error.out
-
-echo "Test: zone file is bogus"
-# since bogusfile doesn't exist anyway, we *don't* specify the directory
-${LOADZONE_PATH}/b10-loadzone -d zone.sqlite3 bogusfile 1> /dev/null 2>> error.out
-
-diff error.out ${TEST_FILE_PATH}/error.known || status=1
-
-echo "Clean tmp file."
-rm -f error.out
-rm -f zone.sqlite3
-
-echo "I:exit status:$status"
-echo "-----------------------------------------------------------------------------"
-echo "Ran 11 test files"
-echo ""
-if [ "$status" -eq 1 ];then
- echo "ERROR"
-else
- echo "OK"
-fi
-exit $status
diff --git a/src/bin/loadzone/tests/error/formerr1.db b/src/bin/loadzone/tests/error/formerr1.db
deleted file mode 100644
index 9bab49f..0000000
--- a/src/bin/loadzone/tests/error/formerr1.db
+++ /dev/null
@@ -1,13 +0,0 @@
-$TTL 300
-$ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-$INCLUDE
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/formerr2.db b/src/bin/loadzone/tests/error/formerr2.db
deleted file mode 100644
index 3d7dd48..0000000
--- a/src/bin/loadzone/tests/error/formerr2.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TTL 300
-com. IN SOA ns.com. hostmaster.com. (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns.example.com.
-ns.com. A 127.0.0.1
-$INCLUDE include.txt sub
-a.com. A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/formerr3.db b/src/bin/loadzone/tests/error/formerr3.db
deleted file mode 100644
index c1c3975..0000000
--- a/src/bin/loadzone/tests/error/formerr3.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TTL
-$ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/formerr4.db b/src/bin/loadzone/tests/error/formerr4.db
deleted file mode 100644
index d37515f..0000000
--- a/src/bin/loadzone/tests/error/formerr4.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TTL M
-$ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/formerr5.db b/src/bin/loadzone/tests/error/formerr5.db
deleted file mode 100644
index fa5983f..0000000
--- a/src/bin/loadzone/tests/error/formerr5.db
+++ /dev/null
@@ -1,13 +0,0 @@
-$TTL 2M
-$ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1 ; ip value
-b "no type error!"
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/include.txt b/src/bin/loadzone/tests/error/include.txt
deleted file mode 100644
index 9b4c57c..0000000
--- a/src/bin/loadzone/tests/error/include.txt
+++ /dev/null
@@ -1 +0,0 @@
-a 300 A 127.0.0.1
diff --git a/src/bin/loadzone/tests/error/keyerror1.db b/src/bin/loadzone/tests/error/keyerror1.db
deleted file mode 100644
index 7384362..0000000
--- a/src/bin/loadzone/tests/error/keyerror1.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TL 300
- at ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/keyerror2.db b/src/bin/loadzone/tests/error/keyerror2.db
deleted file mode 100644
index 5c97e4e..0000000
--- a/src/bin/loadzone/tests/error/keyerror2.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TTL 300
-$OIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/keyerror3.db b/src/bin/loadzone/tests/error/keyerror3.db
deleted file mode 100644
index eebb0aa..0000000
--- a/src/bin/loadzone/tests/error/keyerror3.db
+++ /dev/null
@@ -1,13 +0,0 @@
-$TTL 300
-$ORIGIN com.
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-$INLUDE file.txt
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/originerr1.db b/src/bin/loadzone/tests/error/originerr1.db
deleted file mode 100644
index fc20edc..0000000
--- a/src/bin/loadzone/tests/error/originerr1.db
+++ /dev/null
@@ -1,11 +0,0 @@
-$TTL 300
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/error/originerr2.db b/src/bin/loadzone/tests/error/originerr2.db
deleted file mode 100644
index 2cb90eb..0000000
--- a/src/bin/loadzone/tests/error/originerr2.db
+++ /dev/null
@@ -1,12 +0,0 @@
-$TTL 300
-$ORIGIN com
-@ IN SOA ns hostmaster (
- 1 ; serial
- 3600
- 1800
- 1814400
- 3600
- )
- NS ns
-ns A 127.0.0.1
-a A 10.0.0.1
diff --git a/src/bin/loadzone/tests/loadzone_test.py b/src/bin/loadzone/tests/loadzone_test.py
new file mode 100755
index 0000000..d1ee131
--- /dev/null
+++ b/src/bin/loadzone/tests/loadzone_test.py
@@ -0,0 +1,334 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Tests for the loadzone module'''
+
+import unittest
+from loadzone import *
+from isc.dns import *
+from isc.datasrc import *
+import isc.log
+import bind10_config
+import os
+import shutil
+
+# Some common test parameters
+TESTDATA_PATH = os.environ['TESTDATA_PATH'] + os.sep
+READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
+LOCAL_TESTDATA_PATH = os.environ['LOCAL_TESTDATA_PATH'] + os.sep
+READ_ZONE_DB_FILE = TESTDATA_PATH + "rwtest.sqlite3" # original, to be copied
+NEW_ZONE_TXT_FILE = LOCAL_TESTDATA_PATH + "example.org.zone"
+ALT_NEW_ZONE_TXT_FILE = TESTDATA_PATH + "example.com.zone"
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH'] + os.sep
+WRITE_ZONE_DB_FILE = TESTDATA_WRITE_PATH + "rwtest.sqlite3.copied"
+TEST_ZONE_NAME = Name('example.org')
+DATASRC_CONFIG = '{"database_file": "' + WRITE_ZONE_DB_FILE + '"}'
+
+# before/after SOAs: different in mname and serial
+ORIG_SOA_TXT = 'example.org. 3600 IN SOA ns1.example.org. ' +\
+ 'admin.example.org. 1234 3600 1800 2419200 7200\n'
+NEW_SOA_TXT = 'example.org. 3600 IN SOA ns.example.org. ' +\
+ 'admin.example.org. 1235 3600 1800 2419200 7200\n'
+# This is the brandnew SOA for a newly created zone
+ALT_NEW_SOA_TXT = 'example.com. 3600 IN SOA ns.example.com. ' +\
+ 'admin.example.com. 1234 3600 1800 2419200 7200\n'
+
+class TestLoadZoneRunner(unittest.TestCase):
+ def setUp(self):
+ shutil.copyfile(READ_ZONE_DB_FILE, WRITE_ZONE_DB_FILE)
+
+ # default command line arguments
+ self.__args = ['-c', DATASRC_CONFIG, 'example.org', NEW_ZONE_TXT_FILE]
+ self.__runner = LoadZoneRunner(self.__args)
+
+ def tearDown(self):
+ # Delete the used DB file; if some of the tests unexpectedly fail
+ # unexpectedly in the middle of updating the DB, a lock could stay
+ # there and would affect the other tests that would otherwise succeed.
+ os.unlink(WRITE_ZONE_DB_FILE)
+
+ def test_init(self):
+ '''
+ Checks initial class attributes
+ '''
+ self.assertIsNone(self.__runner._zone_class)
+ self.assertIsNone(self.__runner._zone_name)
+ self.assertIsNone(self.__runner._zone_file)
+ self.assertIsNone(self.__runner._datasrc_config)
+ self.assertIsNone(self.__runner._datasrc_type)
+ self.assertEqual(10000, self.__runner._report_interval)
+ self.assertEqual('INFO', self.__runner._log_severity)
+ self.assertEqual(0, self.__runner._log_debuglevel)
+
+ def test_parse_args(self):
+ self.__runner._parse_args()
+ self.assertEqual(TEST_ZONE_NAME, self.__runner._zone_name)
+ self.assertEqual(NEW_ZONE_TXT_FILE, self.__runner._zone_file)
+ self.assertEqual(DATASRC_CONFIG, self.__runner._datasrc_config)
+ self.assertEqual('sqlite3', self.__runner._datasrc_type) # default
+ self.assertEqual(10000, self.__runner._report_interval) # default
+ self.assertEqual(RRClass.IN(), self.__runner._zone_class) # default
+ self.assertEqual('INFO', self.__runner._log_severity) # default
+ self.assertEqual(0, self.__runner._log_debuglevel)
+
+ def test_set_loglevel(self):
+ runner = LoadZoneRunner(['-d', '1'] + self.__args)
+ runner._parse_args()
+ self.assertEqual('DEBUG', runner._log_severity)
+ self.assertEqual(1, runner._log_debuglevel)
+
+ def test_parse_bad_args(self):
+ # There must be exactly 2 non-option arguments: zone name and zone file
+ self.assertRaises(BadArgument, LoadZoneRunner([])._parse_args)
+ self.assertRaises(BadArgument, LoadZoneRunner(['example']).
+ _parse_args)
+ self.assertRaises(BadArgument, LoadZoneRunner(self.__args + ['0']).
+ _parse_args)
+
+ # Bad zone name
+ args = ['example.org', 'example.zone'] # otherwise valid args
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['bad..name', 'example.zone'] + args).
+ _parse_args)
+
+ # Bad class name
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['-C', 'badclass'] + args).
+ _parse_args)
+ # Unsupported class
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['-C', 'CH'] + args)._parse_args)
+
+ # bad debug level
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['-d', '-10'] + args)._parse_args)
+
+ # bad report interval
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['-i', '-5'] + args)._parse_args)
+
+ # -c cannot be omitted unless it's type sqlite3 (right now)
+ self.assertRaises(BadArgument,
+ LoadZoneRunner(['-t', 'memory'] + args)._parse_args)
+
+ def test_get_datasrc_config(self):
+ # For sqlite3, we use the config with the well-known DB file.
+ expected_conf = \
+ '{"database_file": "' + bind10_config.DATA_PATH + '/zone.sqlite3"}'
+ self.assertEqual(expected_conf,
+ self.__runner._get_datasrc_config('sqlite3'))
+
+ # For other types, config must be given by hand for now
+ self.assertRaises(BadArgument, self.__runner._get_datasrc_config,
+ 'memory')
+
+ def __common_load_setup(self):
+ self.__runner._zone_class = RRClass.IN()
+ self.__runner._zone_name = TEST_ZONE_NAME
+ self.__runner._zone_file = NEW_ZONE_TXT_FILE
+ self.__runner._datasrc_type = 'sqlite3'
+ self.__runner._datasrc_config = DATASRC_CONFIG
+ self.__runner._report_interval = 1
+ self.__reports = []
+ self.__runner._report_progress = lambda x: self.__reports.append(x)
+
+ def __check_zone_soa(self, soa_txt, zone_name=TEST_ZONE_NAME):
+ """Check that the given SOA RR exists and matches the expected string
+
+ If soa_txt is None, the zone is expected to be non-existent.
+ Otherwise, if soa_txt is False, the zone should exist but SOA is
+ expected to be missing.
+
+ """
+
+ client = DataSourceClient('sqlite3', DATASRC_CONFIG)
+ result, finder = client.find_zone(zone_name)
+ if soa_txt is None:
+ self.assertEqual(client.NOTFOUND, result)
+ return
+ self.assertEqual(client.SUCCESS, result)
+ result, rrset, _ = finder.find(zone_name, RRType.SOA())
+ if soa_txt:
+ self.assertEqual(finder.SUCCESS, result)
+ self.assertEqual(soa_txt, rrset.to_text())
+ else:
+ self.assertEqual(finder.NXRRSET, result)
+
+ def test_load_update(self):
+ '''successful case to loading new contents to an existing zone.'''
+ self.__common_load_setup()
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.__runner._do_load()
+ # In this test setup every loaded RR will be reported, and there will
+ # be 3 RRs
+ self.assertEqual([1, 2, 3], self.__reports)
+ self.__check_zone_soa(NEW_SOA_TXT)
+
+ def test_load_update_skipped_report(self):
+ '''successful loading, with reports for every 2 RRs'''
+ self.__common_load_setup()
+ self.__runner._report_interval = 2
+ self.__runner._do_load()
+ self.assertEqual([2], self.__reports)
+
+ def test_load_update_no_report(self):
+ '''successful loading, without progress reports'''
+ self.__common_load_setup()
+ self.__runner._report_interval = 0
+ self.__runner._do_load()
+ self.assertEqual([], self.__reports) # no report
+ self.__check_zone_soa(NEW_SOA_TXT) # but load is completed
+
+ def test_create_and_load(self):
+ '''successful case to loading contents to a new zone (created).'''
+ self.__common_load_setup()
+ self.__runner._zone_name = Name('example.com')
+ self.__runner._zone_file = ALT_NEW_ZONE_TXT_FILE
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+ self.__runner._do_load()
+ self.__check_zone_soa(ALT_NEW_SOA_TXT, zone_name=Name('example.com'))
+
+ def test_load_fail_badconfig(self):
+ '''Load attempt fails due to broken datasrc config.'''
+ self.__common_load_setup()
+ self.__runner._datasrc_config = "invalid config"
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ self.__check_zone_soa(ORIG_SOA_TXT) # no change to the zone
+
+ def test_load_fail_badzone(self):
+ '''Load attempt fails due to broken zone file.'''
+ self.__common_load_setup()
+ self.__runner._zone_file = \
+ LOCAL_TESTDATA_PATH + '/broken-example.org.zone'
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ self.__check_zone_soa(ORIG_SOA_TXT)
+
+ def test_load_fail_noloader(self):
+ '''Load attempt fails because loading isn't supported'''
+ self.__common_load_setup()
+ self.__runner._datasrc_type = 'memory'
+ self.__runner._datasrc_config = '{"type": "memory"}'
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ self.__check_zone_soa(ORIG_SOA_TXT)
+
+ def test_load_fail_create_cancel(self):
+ '''Load attempt fails and new creation of zone is canceled'''
+ self.__common_load_setup()
+ self.__runner._zone_name = Name('example.com')
+ self.__runner._zone_file = 'no-such-file'
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ # _do_load() should have once created the zone but then canceled it.
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+
+ def __common_post_load_setup(self, zone_file):
+ '''Common setup procedure for post load tests which should fail.'''
+ # replace the LoadZoneRunner's original _post_load_warning() for
+ # inspection
+ self.__warnings = []
+ self.__runner._post_load_warning = \
+ lambda msg: self.__warnings.append(msg)
+
+ # perform load and invoke checks
+ self.__common_load_setup()
+ self.__runner._zone_file = zone_file
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ # It fails because there's problem with the zone data
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+
+ def test_load_post_check_fail_soa(self):
+ '''Load succeeds but warns about missing SOA, should cause warn'''
+ self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
+ '/example-nosoa.org.zone')
+
+ def test_load_post_check_fail_ns(self):
+ '''Load succeeds but warns about missing NS, should cause warn'''
+ self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
+ '/example-nons.org.zone')
+
+ def __interrupt_progress(self, loaded_rrs):
+ '''A helper emulating a signal in the middle of loading.
+
+ On the second progress report, it internally invokes the signal
+ handler to see if it stops the loading.
+
+ '''
+ self.__reports.append(loaded_rrs)
+ if len(self.__reports) == 2:
+ self.__runner._interrupt_handler()
+
+ def test_load_interrupted(self):
+ '''Load attempt fails due to signal interruption'''
+ self.__common_load_setup()
+ self.__runner._report_progress = lambda x: self.__interrupt_progress(x)
+ # The interrupting _report_progress() will terminate the loading
+ # in the middle. the number of reports is smaller, and the zone
+ # won't be changed.
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ self.assertEqual([1, 2], self.__reports)
+ self.__check_zone_soa(ORIG_SOA_TXT)
+
+ def test_load_interrupted_create_cancel(self):
+ '''Load attempt for a new zone fails due to signal interruption
+
+ It cancels the zone creation.
+
+ '''
+ self.__common_load_setup()
+ self.__runner._report_progress = lambda x: self.__interrupt_progress(x)
+ self.__runner._zone_name = Name('example.com')
+ self.__runner._zone_file = ALT_NEW_ZONE_TXT_FILE
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+ self.assertRaises(LoadFailure, self.__runner._do_load)
+ self.assertEqual([1, 2], self.__reports)
+ self.__check_zone_soa(None, zone_name=Name('example.com'))
+
+ def test_run_success(self):
+ '''Check for the top-level method.
+
+ Detailed behavior is tested in other tests. We only check the
+ return value of run(), and the zone is successfully loaded.
+
+ '''
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.assertEqual(0, self.__runner.run())
+ self.__check_zone_soa(NEW_SOA_TXT)
+
+ def test_run_fail(self):
+ '''Check for the top-level method, failure case.
+
+ Similar to the success test, but loading will fail, and return
+ value should be 1.
+
+ '''
+ runner = LoadZoneRunner(['-c', DATASRC_CONFIG, 'example.org',
+ LOCAL_TESTDATA_PATH +
+ '/broken-example.org.zone'])
+ self.__check_zone_soa(ORIG_SOA_TXT)
+ self.assertEqual(1, runner.run())
+ self.__check_zone_soa(ORIG_SOA_TXT)
+
+if __name__== "__main__":
+ isc.log.resetUnitTestRootLogger()
+ # Disable the internal logging setup so the test output won't be too
+ # verbose by default.
+ LoadZoneRunner._config_log = lambda x: None
+
+ # Cancel signal handlers so we can stop tests when they hang
+ LoadZoneRunner._set_signal_handlers = lambda x: None
+ unittest.main()
diff --git a/src/bin/loadzone/tests/testdata/broken-example.org.zone b/src/bin/loadzone/tests/testdata/broken-example.org.zone
new file mode 100644
index 0000000..004d617
--- /dev/null
+++ b/src/bin/loadzone/tests/testdata/broken-example.org.zone
@@ -0,0 +1,11 @@
+example.org. 3600 IN SOA (
+ ns.example.org.
+ admin.example.org.
+ 1235
+ 3600 ;1H
+ 1800 ;30M
+ 2419200
+ 7200)
+example.org. 3600 IN NS ns.example.org.
+ns.example.org. 3600 IN A 192.0.2.1
+bad..name.example.org. 3600 IN AAAA 2001:db8::1
diff --git a/src/bin/loadzone/tests/testdata/example-nons.org.zone b/src/bin/loadzone/tests/testdata/example-nons.org.zone
new file mode 100644
index 0000000..f37fa28
--- /dev/null
+++ b/src/bin/loadzone/tests/testdata/example-nons.org.zone
@@ -0,0 +1,10 @@
+;; Intentionally missing SOA for testing post-load checks
+example.org. 3600 IN SOA (
+ ns.example.org.
+ admin.example.org.
+ 1235
+ 3600 ;1H
+ 1800 ;30M
+ 2419200
+ 7200)
+ns.example.org. 3600 IN A 192.0.2.1
diff --git a/src/bin/loadzone/tests/testdata/example-nosoa.org.zone b/src/bin/loadzone/tests/testdata/example-nosoa.org.zone
new file mode 100644
index 0000000..3bf0d0e
--- /dev/null
+++ b/src/bin/loadzone/tests/testdata/example-nosoa.org.zone
@@ -0,0 +1,3 @@
+;; Intentionally missing SOA for testing post-load checks
+example.org. 3600 IN NS ns.example.org.
+ns.example.org. 3600 IN A 192.0.2.1
diff --git a/src/bin/loadzone/tests/testdata/example.org.zone b/src/bin/loadzone/tests/testdata/example.org.zone
new file mode 100644
index 0000000..fc44b36
--- /dev/null
+++ b/src/bin/loadzone/tests/testdata/example.org.zone
@@ -0,0 +1,10 @@
+example.org. 3600 IN SOA (
+ ns.example.org.
+ admin.example.org.
+ 1235
+ 3600 ;1H
+ 1800 ;30M
+ 2419200
+ 7200)
+example.org. 3600 IN NS ns.example.org.
+ns.example.org. 3600 IN A 192.0.2.1
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 4244d07..a49b125 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -4,11 +4,20 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-msgq
+b10_msgqdir = $(pkgdatadir)
+b10_msgq_DATA = msgq.spec
+
CLEANFILES = b10-msgq msgq.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
man_MANS = b10-msgq.8
DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) msgq.xml
+EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes msgq.spec
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
if GENERATE_DOCS
@@ -23,6 +32,11 @@ $(man_MANS):
endif
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py : msgq_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/msgq_messages.mes
+
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-msgq: msgq.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index bab193e..edca400 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -29,34 +29,74 @@ import errno
import time
import select
import random
+import threading
+import isc.config.ccsession
from optparse import OptionParser, OptionValueError
import isc.util.process
+import isc.log
+from isc.log_messages.msgq_messages import *
import isc.cc
isc.util.process.rename()
+isc.log.init("b10-msgq", buffer=True)
+# Logger that is used in the actual msgq handling - startup, shutdown and the
+# poller thread.
+logger = isc.log.Logger("msgq")
+# A separate copy for the master/config thread when the poller thread runs.
+# We use a separate instance, since the logger itself doesn't have to be
+# thread safe.
+config_logger = isc.log.Logger("msgq")
+TRACE_START = logger.DBGLVL_START_SHUT
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
+
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+ SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/msgq"
+else:
+ PREFIX = "@prefix@"
+ DATAROOTDIR = "@datarootdir@"
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = SPECFILE_PATH + "/msgq.spec"
+
class MsgQReceiveError(Exception): pass
class SubscriptionManager:
- def __init__(self):
+ def __init__(self, cfgmgr_ready):
+ """
+ Initialize the subscription manager.
+ parameters:
+ * cfgmgr_ready: A callable object run once the config manager
+ subscribes. This is a hackish solution, but we can't read
+ the configuration sooner.
+ """
self.subscriptions = {}
+ self.__cfgmgr_ready = cfgmgr_ready
+ self.__cfgmgr_ready_called = False
def subscribe(self, group, instance, socket):
"""Add a subscription."""
target = ( group, instance )
if target in self.subscriptions:
- print("[b10-msgq] Appending to existing target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_APPEND_TARGET, group, instance)
if socket not in self.subscriptions[target]:
self.subscriptions[target].append(socket)
else:
- print("[b10-msgq] Creating new target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_NEW_TARGET, group, instance)
self.subscriptions[target] = [ socket ]
+ if group == "ConfigManager" and not self.__cfgmgr_ready_called:
+ logger.debug(TRACE_BASIC, MSGQ_CFGMGR_SUBSCRIBED)
+ self.__cfgmgr_ready_called = True
+ self.__cfgmgr_ready()
def unsubscribe(self, group, instance, socket):
"""Remove the socket from the one specific subscription."""
@@ -124,9 +164,52 @@ class MsgQ:
self.sockets = {}
self.connection_counter = random.random()
self.hostname = socket.gethostname()
- self.subs = SubscriptionManager()
+ self.subs = SubscriptionManager(self.cfgmgr_ready)
self.lnames = {}
self.sendbuffs = {}
+ self.running = False
+ self.__cfgmgr_ready = None
+ self.__cfgmgr_ready_cond = threading.Condition()
+ # A lock used when the message queue does anything more complicated.
+ # It is mostly a safety measure, the threads doing so should be mostly
+ # independent, and the one with config session should be read only,
+ # but with threads, one never knows. We use threads for concurrency,
+ # not for performance, so we use wide lock scopes to be on the safe
+ # side.
+ self.__lock = threading.Lock()
+
+ def cfgmgr_ready(self, ready=True):
+ """Notify that the config manager is either subscribed, or
+ that the msgq is shutting down and it won't connect, but
+ anybody waiting for it should stop anyway.
+
+ The ready parameter signifies if the config manager is subscribed.
+
+ This method can be called multiple times, but second and any
+ following call is simply ignored. This means the "abort" version
+ of the call can be used on any stop unconditionally, even when
+ the config manager already connected.
+ """
+ with self.__cfgmgr_ready_cond:
+ if self.__cfgmgr_ready is not None:
+ # This is a second call to this method. In that case it does
+ # nothing.
+ return
+ self.__cfgmgr_ready = ready
+ self.__cfgmgr_ready_cond.notify_all()
+
+ def wait_cfgmgr(self):
+ """Wait for msgq to subscribe.
+
+ When this returns, the config manager is either subscribed, or
+ msgq gave up waiting for it. Success is signified by the return
+ value.
+ """
+ with self.__cfgmgr_ready_cond:
+ # Wait until it either aborts or subscribes
+ while self.__cfgmgr_ready is None:
+ self.__cfgmgr_ready_cond.wait()
+ return self.__cfgmgr_ready
def setup_poller(self):
"""Set up the poll thing. Internal function."""
@@ -136,7 +219,7 @@ class MsgQ:
self.poller = select.poll()
def add_kqueue_socket(self, socket, write_filter=False):
- """Add a kquque filter for a socket. By default the read
+ """Add a kqueue filter for a socket. By default the read
filter is used; if write_filter is set to True, the write
filter is used. We use a boolean value instead of a specific
filter constant, because kqueue filter values do not seem to
@@ -161,9 +244,7 @@ class MsgQ:
def setup_listener(self):
"""Set up the listener socket. Internal function."""
- if self.verbose:
- sys.stdout.write("[b10-msgq] Setting up socket at %s\n" %
- self.socket_file)
+ logger.debug(TRACE_BASIC, MSGQ_LISTENER_SETUP, self.socket_file)
self.listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -178,8 +259,7 @@ class MsgQ:
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
self.listen_socket.close()
- sys.stderr.write("[b10-msgq] failed to setup listener on %s: %s\n"
- % (self.socket_file, str(e)))
+ logger.fatal(MSGQ_LISTENER_FAILED, self.socket_file, e)
raise e
if self.poller:
@@ -187,6 +267,20 @@ class MsgQ:
else:
self.add_kqueue_socket(self.listen_socket)
+ def setup_signalsock(self):
+ """Create a socket pair used to signal when we want to finish.
+ Using a socket is easy and thread/signal safe way to signal
+ the termination.
+ """
+ # The __poller_sock will be the end in the poller. When it is
+ # closed, we should shut down.
+ (self.__poller_sock, self.__control_sock) = socket.socketpair()
+
+ if self.poller:
+ self.poller.register(self.__poller_sock, select.POLLIN)
+ else:
+ self.add_kqueue_socket(self.__poller_sock)
+
def setup(self):
"""Configure listener socket, polling, etc.
Raises a socket.error if the socket_file cannot be
@@ -194,10 +288,10 @@ class MsgQ:
"""
self.setup_poller()
+ self.setup_signalsock()
self.setup_listener()
- if self.verbose:
- sys.stdout.write("[b10-msgq] Listening\n")
+ logger.debug(TRACE_START, MSGQ_LISTENER_STARTED);
self.runnable = True
@@ -225,7 +319,7 @@ class MsgQ:
def process_socket(self, fd):
"""Process a read on a socket."""
if not fd in self.sockets:
- sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
+ logger.error(MSGQ_READ_UNKNOWN_FD, fd)
return
sock = self.sockets[fd]
# sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
@@ -242,7 +336,7 @@ class MsgQ:
del self.sockets[fd]
if fd in self.sendbuffs:
del self.sendbuffs[fd]
- sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
+ logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
def getbytes(self, fd, sock, length):
"""Get exactly the requested bytes, or raise an exception if
@@ -284,15 +378,15 @@ class MsgQ:
try:
routing, data = self.read_packet(fd, sock)
except MsgQReceiveError as err:
+ logger.error(MSGQ_RECV_ERR, fd, err)
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Receive error: %s\n" % err)
return
try:
routingmsg = isc.cc.message.from_wire(routing)
except DecodeError as err:
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Routing decode error: %s\n" % err)
+ logger.error(MSGQ_HDR_DECODE_ERR, fd, err)
return
self.process_command(fd, sock, routingmsg, data)
@@ -300,9 +394,7 @@ class MsgQ:
def process_command(self, fd, sock, routing, data):
"""Process a single command. This will split out into one of the
other functions."""
- # TODO: A print statement got removed here (one that prints the
- # routing envelope). When we have logging with multiple levels,
- # we might want to re-add that on a high debug verbosity.
+ logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
cmd = routing["type"]
if cmd == 'send':
self.process_command_send(sock, routing, data)
@@ -315,8 +407,10 @@ class MsgQ:
elif cmd == 'ping':
# Command for testing purposes
self.process_command_ping(sock, routing, data)
+ elif cmd == 'stop':
+ self.stop()
else:
- sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
+ logger.error(MSGQ_INVALID_CMD, cmd)
def preparemsg(self, env, msg = None):
if type(env) == dict:
@@ -336,14 +430,34 @@ class MsgQ:
self.send_prepared_msg(sock, self.preparemsg(env, msg))
def __send_data(self, sock, data):
+ """
+ Send a piece of data to the given socket.
+ Parameters:
+ sock: The socket to send to
+ data: The list of bytes to send
+ Returns:
+ An integer or None. If an integer (which can be 0), it signals
+ the number of bytes sent. If None, the socket appears to have
+ been closed on the other end, and it has been killed on this
+ side too.
+ """
try:
# We set the socket nonblocking, MSG_DONTWAIT doesn't exist
# on some OSes
sock.setblocking(0)
return sock.send(data)
except socket.error as e:
- if e.errno == errno.EAGAIN or e.errno == errno.EWOULDBLOCK:
+ if e.errno in [ errno.EAGAIN,
+ errno.EWOULDBLOCK,
+ errno.EINTR ]:
return 0
+ elif e.errno in [ errno.EPIPE,
+ errno.ECONNRESET,
+ errno.ENOBUFS ]:
+ logger.error(MSGQ_SEND_ERR, sock.fileno(),
+ errno.errorcode[e.errno])
+ self.kill_socket(sock.fileno(), sock)
+ return None
else:
raise e
finally:
@@ -356,20 +470,12 @@ class MsgQ:
if fileno in self.sendbuffs:
amount_sent = 0
else:
- try:
- amount_sent = self.__send_data(sock, msg)
- except socket.error as sockerr:
- # in the case the other side seems gone, kill the socket
- # and drop the send action
- if sockerr.errno == errno.EPIPE:
- print("[b10-msgq] SIGPIPE on send, dropping message " +
- "and closing connection")
- self.kill_socket(fileno, sock)
- return
- else:
- raise
+ amount_sent = self.__send_data(sock, msg)
+ if amount_sent is None:
+ # Socket has been killed, drop the send
+ return
- # Still something to send
+ # Still something to send, add it to outgoing queue
if amount_sent < len(msg):
now = time.clock()
# Append it to buffer (but check the data go away)
@@ -394,17 +500,18 @@ class MsgQ:
(_, msg) = self.sendbuffs[fileno]
sock = self.sockets[fileno]
amount_sent = self.__send_data(sock, msg)
- # Keep the rest
- msg = msg[amount_sent:]
- if len(msg) == 0:
- # If there's no more, stop requesting for write availability
- if self.poller:
- self.poller.register(fileno, select.POLLIN)
+ if amount_sent is not None:
+ # Keep the rest
+ msg = msg[amount_sent:]
+ if len(msg) == 0:
+ # If there's no more, stop requesting for write availability
+ if self.poller:
+ self.poller.register(fileno, select.POLLIN)
+ else:
+ self.delete_kqueue_socket(sock, True)
+ del self.sendbuffs[fileno]
else:
- self.delete_kqueue_socket(sock, True)
- del self.sendbuffs[fileno]
- else:
- self.sendbuffs[fileno] = (time.clock(), msg)
+ self.sendbuffs[fileno] = (time.clock(), msg)
def newlname(self):
"""Generate a unique connection identifier for this socket.
@@ -458,6 +565,7 @@ class MsgQ:
def run(self):
"""Process messages. Forever. Mostly."""
+ self.running = True
if self.poller:
self.run_poller()
@@ -465,59 +573,118 @@ class MsgQ:
self.run_kqueue()
def run_poller(self):
- while True:
+ while self.running:
try:
+ # Poll with a timeout so that every once in a while,
+ # the loop checks for self.running.
events = self.poller.poll()
except select.error as err:
if err.args[0] == errno.EINTR:
events = []
else:
- sys.stderr.write("[b10-msgq] Error with poll(): %s\n" % err)
+ logger.fatal(MSGQ_POLL_ERR, err)
break
- for (fd, event) in events:
- if fd == self.listen_socket.fileno():
- self.process_accept()
- else:
- if event & select.POLLOUT:
- self.__process_write(fd)
- if event & select.POLLIN:
- self.process_socket(fd)
+ with self.__lock:
+ for (fd, event) in events:
+ if fd == self.listen_socket.fileno():
+ self.process_accept()
+ elif fd == self.__poller_sock.fileno():
+ # If it's the signal socket, we should terminate now.
+ self.running = False
+ break
+ else:
+ if event & select.POLLOUT:
+ self.__process_write(fd)
+ elif event & select.POLLIN:
+ self.process_socket(fd)
+ else:
+ logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
def run_kqueue(self):
- while True:
+ while self.running:
+ # Check with a timeout so that every once in a while,
+ # the loop checks for self.running.
events = self.kqueue.control(None, 10)
if not events:
raise RuntimeError('serve: kqueue returned no events')
- for event in events:
- if event.ident == self.listen_socket.fileno():
- self.process_accept()
- else:
- if event.filter == select.KQ_FILTER_WRITE:
- self.__process_write(event.ident)
- if event.filter == select.KQ_FILTER_READ and \
- event.data > 0:
- self.process_socket(event.ident)
- elif event.flags & select.KQ_EV_EOF:
- self.kill_socket(event.ident,
- self.sockets[event.ident])
+ with self.__lock:
+ for event in events:
+ if event.ident == self.listen_socket.fileno():
+ self.process_accept()
+ elif event.ident == self.__poller_sock.fileno():
+ # If it's the signal socket, we should terminate now.
+ self.running = False
+ break;
+ else:
+ if event.filter == select.KQ_FILTER_WRITE:
+ self.__process_write(event.ident)
+ if event.filter == select.KQ_FILTER_READ and \
+ event.data > 0:
+ self.process_socket(event.ident)
+ elif event.flags & select.KQ_EV_EOF:
+ self.kill_socket(event.ident,
+ self.sockets[event.ident])
+
+ def stop(self):
+ # Signal it should terminate.
+ self.__control_sock.close()
+ self.__control_sock = None
+ # Abort anything waiting on the condition, just to make sure it's not
+ # blocked forever
+ self.cfgmgr_ready(False)
+
+ def cleanup_signalsock(self):
+ """Close the signal sockets. We could do it directly in shutdown,
+ but this part is reused in tests.
+ """
+ if self.__poller_sock:
+ self.__poller_sock.close()
+ self.__poller_sock = None
+ if self.__control_sock:
+ self.__control_sock.close()
+ self.__control_sock = None
def shutdown(self):
"""Stop the MsgQ master."""
- if self.verbose:
- sys.stdout.write("[b10-msgq] Stopping the server.\n")
+ logger.debug(TRACE_START, MSGQ_SHUTDOWN)
self.listen_socket.close()
+ self.cleanup_signalsock()
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
-# can signal handling and calling a destructor be done without a
-# global variable?
-msgq = None
+ def config_handler(self, new_config):
+ """The configuration handler (run in a separate thread).
+ Not tested, currently effectively empty.
+ """
+ config_logger.debug(TRACE_DETAIL, MSGQ_CONFIG_DATA, new_config)
+
+ with self.__lock:
+ if not self.running:
+ return
+
+ # TODO: Any config handlig goes here.
-def signal_handler(signal, frame):
+ return isc.config.create_answer(0)
+
+ def command_handler(self, command, args):
+ """The command handler (run in a separate thread).
+ Not tested, currently effectively empty.
+ """
+ config_logger.debug(TRACE_DETAIL, MSGQ_COMMAND, command, args)
+
+ with self.__lock:
+ if not self.running:
+ return
+
+ # TODO: Any commands go here
+
+ config_logger.error(MSGQ_COMMAND_UNKNOWN, command)
+ return isc.config.create_answer(1, 'unknown command: ' + command)
+
+def signal_handler(msgq, signal, frame):
if msgq:
- msgq.shutdown()
- sys.exit(0)
+ msgq.stop()
if __name__ == "__main__":
def check_port(option, opt_str, value, parser):
@@ -530,6 +697,7 @@ if __name__ == "__main__":
# Parse any command-line options.
parser = OptionParser(version=VERSION)
+ # TODO: Should we remove the option?
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="display more about what is going on")
parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -537,22 +705,46 @@ if __name__ == "__main__":
help="UNIX domain socket file the msgq daemon will use")
(options, args) = parser.parse_args()
- signal.signal(signal.SIGTERM, signal_handler)
-
# Announce startup.
- if options.verbose:
- sys.stdout.write("[b10-msgq] %s\n" % VERSION)
+ logger.debug(TRACE_START, MSGQ_START, VERSION)
msgq = MsgQ(options.msgq_socket_file, options.verbose)
+ signal.signal(signal.SIGTERM,
+ lambda signal, frame: signal_handler(msgq, signal, frame))
+
try:
msgq.setup()
except Exception as e:
- sys.stderr.write("[b10-msgq] Error on startup: %s\n" % str(e))
+ logger.fatal(MSGQ_START_FAIL, e)
sys.exit(1)
+ # We run the processing in a separate thread. This is because we want to
+ # connect to the msgq ourself. But the cc library is unfortunately blocking
+ # in many places and waiting for the processing part to answer, it would
+ # deadlock.
+ poller_thread = threading.Thread(target=msgq.run)
+ poller_thread.daemon = True
try:
- msgq.run()
+ poller_thread.start()
+ if msgq.wait_cfgmgr():
+ # Once we get the config manager, we can read our own config.
+ session = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+ msgq.config_handler,
+ msgq.command_handler,
+ None, True,
+ msgq.socket_file)
+ session.start()
+ # And we create a thread that'll just wait for commands and
+ # handle them. We don't terminate the thread, we set it to
+ # daemon. Once the main thread terminates, it'll just die.
+ def run_session():
+ while True:
+ session.check_command(False)
+ background_thread = threading.Thread(target=run_session)
+ background_thread.daemon = True
+ background_thread.start()
+ poller_thread.join()
except KeyboardInterrupt:
pass
diff --git a/src/bin/msgq/msgq.spec b/src/bin/msgq/msgq.spec
new file mode 100644
index 0000000..93204fa
--- /dev/null
+++ b/src/bin/msgq/msgq.spec
@@ -0,0 +1,8 @@
+{
+ "module_spec": {
+ "module_name": "Msgq",
+ "module_description": "The message queue",
+ "config_data": [],
+ "commands": []
+ }
+}
diff --git a/src/bin/msgq/msgq.xml b/src/bin/msgq/msgq.xml
index fed4c27..43c72a4 100644
--- a/src/bin/msgq/msgq.xml
+++ b/src/bin/msgq/msgq.xml
@@ -111,7 +111,7 @@
<listitem><para>
The UNIX domain socket file this daemon will use.
The default is
- <filename>/usr/local/var/bind10-devel/msg_socket</filename>.
+ <filename>/usr/local/var/bind10/msg_socket</filename>.
<!-- @localstatedir@/@PACKAGE_NAME@/msg_socket -->
</para></listitem>
</varlistentry>
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
new file mode 100644
index 0000000..75e4227
--- /dev/null
+++ b/src/bin/msgq/msgq_messages.mes
@@ -0,0 +1,106 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the ddns messages python module.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MSGQ_CFGMGR_SUBSCRIBED The config manager subscribed to message queue
+This is a debug message. The message queue has little bit of special handling
+for the configuration manager. This special handling is happening now.
+
+% MSGQ_COMMAND Running command %1 with arguments %2
+Debug message. The message queue received a command and it is running it.
+
+% MSGQ_COMMAND_UNKNOWN Unknown command '%1'
+The message queue received a command from other module, but it doesn't
+recognize it. This is probably either a coding error or inconsistency between
+the message queue version and version of the module.
+
+% MSGQ_CONFIG_DATA Received configuration update for the msgq: %1
+Debug message. The message queue received a configuration update, handling it.
+
+% MSGQ_HDR_DECODE_ERR Error decoding header received from socket %1: %2
+The socket with mentioned file descriptor sent a packet. However, it was not
+possible to decode the routing header of the packet. The packet is ignored.
+This may be caused by a programmer error (one of the components sending invalid
+data) or possibly by incompatible version of msgq and the component (but that's
+unlikely, as the protocol is not changed often).
+
+% MSGQ_LISTENER_FAILED Failed to initialize listener on socket file '%1': %2
+The message queue daemon tried to listen on a file socket (the path is in the
+message), but it failed. The error from the operating system is logged.
+
+% MSGQ_LISTENER_SETUP Starting to listen on socket file '%1'
+Debug message. The listener is trying to open a listening socket.
+
+% MSGQ_LISTENER_STARTED Successfully started to listen
+Debug message. The message queue successfully opened a listening socket and
+waits for incoming connections.
+
+% MSGQ_POLL_ERR Error while polling for events: %1
+A low-level error happened when waiting for events, the error is logged. The
+reason for this varies, but it usually means the system is short on some
+resources.
+
+% MSGQ_POLL_UNKNOWN_EVENT Got an unknown event from the poller for fd %1: %2
+An unknown event got out from the poll() system call. This should generally not
+happen and it is either a programmer error or OS bug. The event is ignored. The
+number noted as the event is the raw encoded value, which might be useful to
+the authors when figuring the problem out.
+
+% MSGQ_READ_UNKNOWN_FD Got read on strange socket %1
+The OS reported a file descriptor is ready to read. But the daemon doesn't know
+the mentioned file descriptor, which is either a programmer error or OS bug.
+The read event is ignored.
+
+% MSGQ_RECV_ERR Error reading from socket %1: %2
+There was a low-level error when reading from a socket. The error is logged and
+the corresponding socket is dropped.
+
+% MSGQ_RECV_HDR Received header: %1
+Debug message. This message includes the whole routing header of a packet.
+
+% MSGQ_INVALID_CMD Received invalid command: %1
+An unknown command listed in the log has been received. It is ignored. This
+indicates either a programmer error (eg. a typo in the command name) or
+incompatible version of a module and message queue daemon.
+
+% MSGQ_SEND_ERR Error while sending to socket %1: %2
+There was a low-level error when sending data to a socket. The error is logged
+and the corresponding socket is dropped.
+
+% MSGQ_SHUTDOWN Stopping Msgq
+Debug message. The message queue is shutting down.
+
+% MSGQ_SOCK_CLOSE Closing socket fd %1
+Debug message. Closing the mentioned socket.
+
+% MSGQ_START Msgq version %1 starting
+Debug message. The message queue is starting up.
+
+% MSGQ_START_FAIL Error during startup: %1
+There was an error during early startup of the daemon. More concrete error is
+in the log. The daemon terminates as a result.
+
+% MSGQ_SUBS_APPEND_TARGET Appending to existing target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription by appending it to already existing
+data structure.
+
+% MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription. Also creating a new data structure
+to hold it.
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index 3e464be..3ab4024 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,9 +20,25 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/log/.libs
export PYTHONPATH
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index 50b218b..c9ef5d3 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -21,6 +21,8 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/msgq \
BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/msgq/tests/msgq_test.in b/src/bin/msgq/tests/msgq_test.in
deleted file mode 100755
index e3aae89..0000000
--- a/src/bin/msgq/tests/msgq_test.in
+++ /dev/null
@@ -1,28 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
-export PYTHON_EXEC
-
-MYPATH_PATH=@abs_top_srcdir@/src/bin/msgq/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/lib/python
-
-export PYTHONPATH
-
-cd ${MYPATH_PATH}
-exec ${PYTHON_EXEC} -O msgq_test.py $*
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 6dc7d1c..00e15d8 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -6,7 +6,11 @@ import socket
import signal
import sys
import time
+import errno
+import threading
import isc.cc
+import collections
+import isc.log
#
# Currently only the subscription part and some sending is implemented...
@@ -15,7 +19,12 @@ import isc.cc
class TestSubscriptionManager(unittest.TestCase):
def setUp(self):
- self.sm = SubscriptionManager()
+ self.__cfgmgr_ready_called = 0
+ self.sm = SubscriptionManager(self.cfgmgr_ready)
+
+ def cfgmgr_ready(self):
+ # Called one more time
+ self.__cfgmgr_ready_called += 1
def test_subscription_add_delete_manager(self):
self.sm.subscribe("a", "*", 'sock1')
@@ -97,7 +106,7 @@ class TestSubscriptionManager(unittest.TestCase):
try:
msgq.setup()
self.assertTrue(os.path.exists(socket_file))
- msgq.shutdown();
+ msgq.shutdown()
self.assertFalse(os.path.exists(socket_file))
except socket.error:
# ok, the install path doesn't exist at all,
@@ -111,6 +120,103 @@ class TestSubscriptionManager(unittest.TestCase):
def test_open_socket_bad(self):
msgq = MsgQ("/does/not/exist")
self.assertRaises(socket.error, msgq.setup)
+ # But we can clean up after that.
+ msgq.shutdown()
+
+ def test_subscribe_cfgmgr(self):
+ """Test special handling of the config manager. Once it subscribes,
+ the message queue needs to connect and read the config. But not
+ before and only once.
+ """
+ self.assertEqual(0, self.__cfgmgr_ready_called)
+ # Not called when something else subscribes
+ self.sm.subscribe('SomethingElse', '*', 's1')
+ self.assertEqual(0, self.__cfgmgr_ready_called)
+ # Called whenever the config manager subscribes
+ self.sm.subscribe('ConfigManager', '*', 's2')
+ self.assertEqual(1, self.__cfgmgr_ready_called)
+ # But not called again when it subscribes again (should not
+ # happen in practice, but we make sure anyway)
+ self.sm.subscribe('ConfigManager', '*', 's3')
+ self.assertEqual(1, self.__cfgmgr_ready_called)
+
+class DummySocket:
+ """
+ Dummy socket class.
+ This one does nothing at all, but some calls are used.
+ It is mainly intended to override the listen socket for msgq, which
+ we do not need in these tests.
+ """
+ def fileno():
+ return -1
+
+ def close():
+ pass
+
+class BadSocket:
+ """
+ Special socket wrapper class. Once given a socket in its constructor,
+ it completely behaves like that socket, except that its send() call
+ will only actually send one byte per call, and optionally raise a given
+ exception at a given time.
+ """
+ def __init__(self, real_socket, raise_on_send=0, send_exception=None):
+ """
+ Parameters:
+ real_socket: The actual socket to wrap
+ raise_on_send: integer. If higher than 0, and send_exception is
+ not None, send_exception will be raised on the
+ 'raise_on_send'th call to send().
+ send_exception: if not None, this exception will be raised
+ (if raise_on_send is not 0)
+ """
+ self.socket = real_socket
+ self.send_count = 0
+ self.raise_on_send = raise_on_send
+ self.send_exception = send_exception
+
+ # completely wrap all calls and member access
+ # (except explicitely overridden ones)
+ def __getattr__(self, name, *args):
+ attr = getattr(self.socket, name)
+ if isinstance(attr, collections.Callable):
+ def callable_attr(*args):
+ return attr.__call__(*args)
+ return callable_attr
+ else:
+ return attr
+
+ def send(self, data):
+ self.send_count += 1
+ if self.send_exception is not None and\
+ self.send_count == self.raise_on_send:
+ raise self.send_exception
+
+ if len(data) > 0:
+ return self.socket.send(data[:1])
+ else:
+ return 0
+
+class MsgQThread(threading.Thread):
+ """
+ Very simple thread class that runs msgq.run() when started,
+ and stores the exception that msgq.run() raises, if any.
+ """
+ def __init__(self, msgq):
+ threading.Thread.__init__(self)
+ self.msgq_ = msgq
+ self.caught_exception = None
+ self.lock = threading.Lock()
+
+ def run(self):
+ try:
+ self.msgq_.run()
+ except Exception as exc:
+ # Store the exception to make the test fail if necessary
+ self.caught_exception = exc
+
+ def stop(self):
+ self.msgq_.stop()
class SendNonblock(unittest.TestCase):
"""
@@ -191,9 +297,6 @@ class SendNonblock(unittest.TestCase):
msgq = MsgQ()
# msgq.run needs to compare with the listen_socket, so we provide
# a replacement
- class DummySocket:
- def fileno():
- return -1
msgq.listen_socket = DummySocket
(queue, out) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
def run():
@@ -202,8 +305,10 @@ class SendNonblock(unittest.TestCase):
if queue_pid == 0:
signal.alarm(120)
msgq.setup_poller()
+ msgq.setup_signalsock()
msgq.register_socket(queue)
msgq.run()
+ msgq.cleanup_signalsock()
else:
try:
def killall(signum, frame):
@@ -245,5 +350,217 @@ class SendNonblock(unittest.TestCase):
data = data + data
self.send_many(data)
+ def do_send(self, write, read, control_write, control_read,
+ expect_arrive=True, expect_send_exception=None):
+ """
+ Makes a msgq object that is talking to itself,
+ run it in a separate thread so we can use and
+ test run().
+ It is given two sets of connected sockets; write/read, and
+ control_write/control_read. The former may be throwing errors
+ and mangle data to test msgq. The second is mainly used to
+ send msgq the stop command.
+ (Note that the terms 'read' and 'write' are from the msgq
+ point of view, so the test itself writes to 'control_read')
+ Parameters:
+ write: a socket that is used to send the data to
+ read: a socket that is used to read the data from
+ control_write: a second socket for communication with msgq
+ control_read: a second socket for communication with msgq
+ expect_arrive: if True, the read socket is read from, and the data
+ that is read is expected to be the same as the data
+ that has been sent to the write socket.
+ expect_send_exception: if not None, this is the exception that is
+ expected to be raised by msgq
+ """
+
+ # Some message and envelope data to send and check
+ env = b'{"env": "foo"}'
+ msg = b'{"msg": "bar"}'
+
+ msgq = MsgQ()
+ # Don't need a listen_socket
+ msgq.listen_socket = DummySocket
+ msgq.setup_poller()
+ msgq.setup_signalsock()
+ msgq.register_socket(write)
+ msgq.register_socket(control_write)
+ # Queue the message for sending
+ msgq.sendmsg(write, env, msg)
+
+ # Run it in a thread
+ msgq_thread = MsgQThread(msgq)
+ # If we're done, just kill it
+ msgq_thread.start()
+
+ if expect_arrive:
+ (recv_env, recv_msg) = msgq.read_packet(read.fileno(),
+ read)
+ self.assertEqual(env, recv_env)
+ self.assertEqual(msg, recv_msg)
+
+ # Tell msgq to stop
+ msg = msgq.preparemsg({"type" : "stop"})
+ control_read.sendall(msg)
+
+ # Wait for thread to stop if it hasn't already.
+ # Put in a (long) timeout; the thread *should* stop, but if it
+ # does not, we don't want the test to hang forever
+ msgq_thread.join(60)
+ # Fail the test if it didn't stop
+ self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
+
+ # Clean up some internals of msgq (usually called as part of
+ # shutdown, but we skip that one here)
+ msgq.cleanup_signalsock()
+
+ # Check the exception from the thread, if any
+ # First, if we didn't expect it; reraise it (to make test fail and
+ # show the stacktrace for debugging)
+ if expect_send_exception is None:
+ if msgq_thread.caught_exception is not None:
+ raise msgq_thread.caught_exception
+ else:
+ # If we *did* expect it, fail it there was none
+ self.assertIsNotNone(msgq_thread.caught_exception)
+
+ def do_send_with_send_error(self, raise_on_send, send_exception,
+ expect_answer=True,
+ expect_send_exception=None):
+ """
+ Sets up two connected sockets, wraps the sender socket into a BadSocket
+ class, then performs a do_send() test.
+ Parameters:
+ raise_on_send: the byte at which send_exception should be raised
+ (see BadSocket)
+ send_exception: the exception to raise (see BadSocket)
+ expect_answer: whether the send is expected to complete (and hence
+ the read socket should get the message)
+ expect_send_exception: the exception msgq is expected to raise when
+ send_exception is raised by BadSocket.
+ """
+ (write, read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ (control_write, control_read) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM)
+ badwrite = BadSocket(write, raise_on_send, send_exception)
+ self.do_send(badwrite, read, control_write, control_read, expect_answer, expect_send_exception)
+ write.close()
+ read.close()
+ control_write.close()
+ control_read.close()
+
+ def test_send_raise_recoverable(self):
+ """
+ Test whether msgq survices a recoverable socket errors when sending.
+ Two tests are done: one where the error is raised on the 3rd octet,
+ and one on the 23rd.
+ """
+ sockerr = socket.error
+ for err in [ errno.EAGAIN, errno.EWOULDBLOCK, errno.EINTR ]:
+ sockerr.errno = err
+ self.do_send_with_send_error(3, sockerr)
+ self.do_send_with_send_error(23, sockerr)
+
+ def test_send_raise_nonrecoverable(self):
+ """
+ Test whether msgq survives socket errors that are nonrecoverable
+ (for said socket that is, i.e. EPIPE etc).
+ Two tests are done: one where the error is raised on the 3rd octet,
+ and one on the 23rd.
+ """
+ sockerr = socket.error
+ for err in [ errno.EPIPE, errno.ENOBUFS, errno.ECONNRESET ]:
+ sockerr.errno = err
+ self.do_send_with_send_error(3, sockerr, False)
+ self.do_send_with_send_error(23, sockerr, False)
+
+ def otest_send_raise_crash(self):
+ """
+ Test whether msgq does NOT survive on a general exception.
+ Note, perhaps it should; but we'd have to first discuss and decide
+ how it should recover (i.e. drop the socket and consider the client
+ dead?
+ It may be a coding problem in msgq itself, and we certainly don't
+ want to ignore those.
+ """
+ sockerr = Exception("just some general exception")
+ self.do_send_with_send_error(3, sockerr, False, sockerr)
+ self.do_send_with_send_error(23, sockerr, False, sockerr)
+
+class ThreadTests(unittest.TestCase):
+ """Test various things around thread synchronization."""
+
+ def setUp(self):
+ self.__msgq = MsgQ()
+ self.__abort_wait = False
+ self.__result = None
+ self.__notify_thread = threading.Thread(target=self.__notify)
+ self.__wait_thread = threading.Thread(target=self.__wait)
+ # Make sure the threads are killed if left behind by the test.
+ self.__notify_thread.daemon = True
+ self.__wait_thread.daemon = True
+
+ def __notify(self):
+ """Call the cfgmgr_ready."""
+ if self.__abort_wait:
+ self.__msgq.cfgmgr_ready(False)
+ else:
+ self.__msgq.cfgmgr_ready()
+
+ def __wait(self):
+ """Wait for config manager and store the result."""
+ self.__result = self.__msgq.wait_cfgmgr()
+
+ def test_wait_cfgmgr(self):
+ """One thread signals the config manager subscribed, the other
+ waits for it. We then check it terminated correctly.
+ """
+ self.__notify_thread.start()
+ self.__wait_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertTrue(self.__result)
+
+ def test_wait_cfgmgr_2(self):
+ """Same as test_wait_cfgmgr, but starting the threads in reverse order
+ (the result should be the same).
+ """
+ self.__wait_thread.start()
+ self.__notify_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertTrue(self.__result)
+
+ def test_wait_abort(self):
+ """Similar to test_wait_cfgmgr, but the config manager is never
+ subscribed and it is aborted.
+ """
+ self.__abort_wait = True
+ self.__wait_thread.start()
+ self.__notify_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertIsNotNone(self.__result)
+ self.assertFalse(self.__result)
+
+ def __check_ready_and_abort(self):
+ """Check that when we first say the config manager is ready and then
+ try to abort, it uses the first result.
+ """
+ self.__msgq.cfgmgr_ready()
+ self.__msgq.cfgmgr_ready(False)
+ self.__result = self.__msgq.wait_cfgmgr()
+
+ def test_ready_and_abort(self):
+ """Perform the __check_ready_and_abort test, but in a separate thread,
+ so in case something goes wrong with the synchronisation and it
+ deadlocks, the test will terminate anyway.
+ """
+ test_thread = threading.Thread(target=self.__check_ready_and_abort)
+ test_thread.daemon = True
+ test_thread.start()
+ test_thread.join(60)
+ self.assertTrue(self.__result)
+
if __name__ == '__main__':
+ isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/resolver/b10-resolver.xml b/src/bin/resolver/b10-resolver.xml
index aca8fb2..485d022 100644
--- a/src/bin/resolver/b10-resolver.xml
+++ b/src/bin/resolver/b10-resolver.xml
@@ -20,7 +20,7 @@
<refentry>
<refentryinfo>
- <date>February 28, 2012</date>
+ <date>August 16, 2012</date>
</refentryinfo>
<refmeta>
@@ -148,6 +148,8 @@ once that is merged you can for instance do 'config add Resolver/forward_address
address or special keyword.
The <varname>key</varname> is a TSIG key name.
The default configuration accepts queries from 127.0.0.1 and ::1.
+ The default action is REJECT for newly added
+ <varname>query_acl</varname> items.
</para>
<para>
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index d14fb0b..457f285 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.cc
@@ -143,7 +143,7 @@ main(int argc, char* argv[]) {
// temporary initLogger() code. If verbose, we'll use maximum verbosity.
isc::log::initLogger(RESOLVER_NAME,
(verbose ? isc::log::DEBUG : isc::log::INFO),
- isc::log::MAX_DEBUG_LEVEL, NULL);
+ isc::log::MAX_DEBUG_LEVEL, NULL, true);
// Print the starting message
string cmdline = argv[0];
@@ -177,7 +177,7 @@ main(int argc, char* argv[]) {
isc::cache::ResolverCache cache;
resolver->setCache(cache);
-
+
// TODO priming query, remove root from direct
// Fake a priming query result here (TODO2 how to flag non-expiry?)
// propagation to runningquery. And check for forwarder mode?
@@ -185,21 +185,21 @@ main(int argc, char* argv[]) {
isc::dns::Name("."),
isc::dns::RRClass::IN(),
isc::dns::RRType::NS()));
- isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
+ isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
isc::dns::RRClass::IN(),
isc::dns::RRType::NS(),
isc::dns::RRTTL(8888)));
root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
isc::dns::RRClass::IN(),
"l.root-servers.net."));
- isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
isc::dns::RRClass::IN(),
isc::dns::RRType::A(),
isc::dns::RRTTL(8888)));
root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
isc::dns::RRClass::IN(),
"199.7.83.42"));
- isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
isc::dns::RRClass::IN(),
isc::dns::RRType::AAAA(),
isc::dns::RRTTL(8888)));
@@ -216,7 +216,7 @@ main(int argc, char* argv[]) {
cache.update(root_ns_rrset);
cache.update(root_a_rrset);
cache.update(root_aaaa_rrset);
-
+
DNSService dns_service(io_service, checkin, lookup, answer);
resolver->setDNSService(dns_service);
LOG_DEBUG(resolver_logger, RESOLVER_DBG_INIT, RESOLVER_SERVICE_CREATED);
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index cc0f09f..725aa85 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -200,14 +200,14 @@ public:
/**
* \short Get info about timeouts.
*
- * \returns Timeout and retries (as described in setTimeouts).
+ * \return Timeout and retries (as described in setTimeouts).
*/
std::pair<int, unsigned> getTimeouts() const;
/**
* \brief Get the timeout for outgoing queries
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getQueryTimeout() const;
@@ -218,7 +218,7 @@ public:
* (internal resolving on the query will continue, see
* \c getLookupTimeout())
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getClientTimeout() const;
diff --git a/src/bin/stats/b10-stats-httpd.xml b/src/bin/stats/b10-stats-httpd.xml
index 17b7671..28d6ac9 100644
--- a/src/bin/stats/b10-stats-httpd.xml
+++ b/src/bin/stats/b10-stats-httpd.xml
@@ -103,7 +103,7 @@
<refsect1>
<title>FILES</title>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd.spec</filename>
+ <filename>/usr/local/share/bind10/stats-httpd.spec</filename>
<!--TODO: The filename should be computed from prefix-->
— the spec file of <command>b10-stats-httpd</command>. This file
contains configurable settings
@@ -115,17 +115,17 @@
how to configure the settings.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xml.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xml.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XML document.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xsd.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xsd.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XSD document.
</para>
<para>
- <filename>/usr/local/share/bind10-devel/stats-httpd-xsl.tpl</filename>
+ <filename>/usr/local/share/bind10/stats-httpd-xsl.tpl</filename>
<!--TODO: The filename should be computed from prefix-->
— the template file of XSL document.
</para>
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index 88a01ed..ee89ad2 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -210,7 +210,7 @@
<refsect1>
<title>FILES</title>
- <para><filename>/usr/local/share/bind10-devel/stats.spec</filename>
+ <para><filename>/usr/local/share/bind10/stats.spec</filename>
<!--TODO: The filename should be computed from prefix-->
— This is a spec file for <command>b10-stats</command>. It
contains commands for <command>b10-stats</command>. They can be
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 36e028a..7123c53 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -31,7 +31,7 @@ import isc.util.process
import isc.log
from isc.log_messages.stats_messages import *
-isc.log.init("b10-stats")
+isc.log.init("b10-stats", buffer=True)
logger = isc.log.Logger("stats")
# Some constants for debug levels.
@@ -682,7 +682,7 @@ if __name__ == "__main__":
help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
- isc.log.init("b10-stats", "DEBUG", 99)
+ isc.log.init("b10-stats", "DEBUG", 99, buffer=True)
stats = Stats()
stats.start()
except OptionValueError as ove:
diff --git a/src/bin/stats/stats_httpd.py.in b/src/bin/stats/stats_httpd.py.in
index 63eb0f3..057b8ca 100644
--- a/src/bin/stats/stats_httpd.py.in
+++ b/src/bin/stats/stats_httpd.py.in
@@ -39,7 +39,7 @@ import isc.util.process
import isc.log
from isc.log_messages.stats_httpd_messages import *
-isc.log.init("b10-stats-httpd")
+isc.log.init("b10-stats-httpd", buffer=True)
logger = isc.log.Logger("stats-httpd")
# Some constants for debug levels.
@@ -609,7 +609,7 @@ if __name__ == "__main__":
help="enable maximum debug logging")
(options, args) = parser.parse_args()
if options.verbose:
- isc.log.init("b10-stats-httpd", "DEBUG", 99)
+ isc.log.init("b10-stats-httpd", "DEBUG", 99, buffer=True)
stats_httpd = StatsHttpd()
stats_httpd.start()
except OptionValueError as ove:
diff --git a/src/bin/xfrin/b10-xfrin.xml b/src/bin/xfrin/b10-xfrin.xml
index 231681c..eb16ab3 100644
--- a/src/bin/xfrin/b10-xfrin.xml
+++ b/src/bin/xfrin/b10-xfrin.xml
@@ -112,20 +112,18 @@ in separate zonemgr process.
<varname>master_addr</varname> (the zone master to transfer from),
<varname>master_port</varname> (defaults to 53),
<varname>use_ixfr</varname> (defaults to false), and
- <varname>tsig_key</varname> (optional TSIG key to use).
- The <varname>tsig_key</varname> is specified using a full string
- colon-delimited name:key:algorithm representation (e.g.
- <quote>foo.example.org:EvABsfU2h7uofnmqaRCrhHunGsd=:hmac-sha1</quote>).
+ <varname>tsig_key</varname> (optional TSIG key name to use).
+ The <varname>tsig_key</varname> is specified using a name that
+ corresponds to one of the TSIG keys configured in the global
+ TSIG key ring (<quote>/tsig_keys/keys</quote>).
</para>
<!-- TODO: document this better -->
-<!-- TODO: the tsig_key format may change -->
<para>
(The site-wide <varname>master_addr</varname> and
<varname>master_port</varname> configurations are deprecated;
use the <varname>zones</varname> list configuration instead.)
</para>
-<!-- NOTE: also tsig_key but not mentioning since so short lived. -->
<!-- TODO: formating -->
<para>
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index 4e8d6ab..99c5e1e 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -26,6 +26,7 @@ from xfrin import *
import xfrin
from isc.xfrin.diff import Diff
import isc.log
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
# If we use any python library that is basically a wrapper for
# a library we use as well (like sqlite3 in our datasources),
# we must make sure we import ours first; If we have special
@@ -59,7 +60,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1234 3600 1800 2419200 7200')
soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
soa_rrset.add_rdata(soa_rdata)
@@ -67,7 +68,7 @@ soa_rrset.add_rdata(soa_rdata)
# SOA intended to be used for the current SOA at the secondary side.
# Note that its serial is smaller than that of soa_rdata.
begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1230 3600 1800 2419200 7200')
begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
begin_soa_rrset.add_rdata(begin_soa_rdata)
@@ -139,6 +140,16 @@ class MockCC(MockModuleCCSession):
if identifier == "zones/use_ixfr":
return False
+ def add_remote_config_by_name(self, name, callback):
+ pass
+
+ def get_remote_config_value(self, module, identifier):
+ if module == 'tsig_keys' and identifier == 'keys':
+ return (['example.com.key.:EvAAsfU2h7uofnmqaTCrhHunGsc='], True)
+ else:
+ raise Exception('MockCC requested for unknown config value ' +
+ + module + "/" + identifier)
+
def remove_remote_config(self, module_name):
pass
@@ -229,6 +240,7 @@ class MockXfrin(Xfrin):
def _cc_setup(self):
self._tsig_key = None
self._module_cc = MockCC()
+ init_keyring(self._module_cc)
pass
def _get_db_file(self):
@@ -2427,9 +2439,10 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(str(zone_info.master_addr), zone_config['master_addr'])
self.assertEqual(zone_info.master_port, zone_config['master_port'])
if 'tsig_key' in zone_config:
- self.assertEqual(zone_info.tsig_key.to_text(), TSIGKey(zone_config['tsig_key']).to_text())
+ self.assertEqual(zone_info.tsig_key_name.to_text(),
+ Name(zone_config['tsig_key']).to_text())
else:
- self.assertIsNone(zone_info.tsig_key)
+ self.assertIsNone(zone_info.tsig_key_name)
if 'use_ixfr' in zone_config and\
zone_config.get('use_ixfr'):
self.assertTrue(zone_info.use_ixfr)
@@ -2562,7 +2575,7 @@ class TestXfrin(unittest.TestCase):
{ 'name': 'test2.example.',
'master_addr': '192.0.2.9',
'master_port': 53,
- 'tsig_key': 'badkey'
+ 'tsig_key': 'badkey..'
}
]}
self.assertEqual(self.xfr.config_handler(zones)['result'][0], 1)
@@ -2581,13 +2594,14 @@ class TestXfrin(unittest.TestCase):
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self._check_zones_config(config)
- def common_ixfr_setup(self, xfr_mode, use_ixfr):
+ def common_ixfr_setup(self, xfr_mode, use_ixfr, tsig_key_str = None):
# This helper method explicitly sets up a zone configuration with
# use_ixfr, and invokes either retransfer or refresh.
# Shared by some of the following test cases.
config = {'zones': [
{'name': 'example.com.',
'master_addr': '192.0.2.1',
+ 'tsig_key': tsig_key_str,
'use_ixfr': use_ixfr}]}
self.assertEqual(self.xfr.config_handler(config)['result'][0], 0)
self.assertEqual(self.xfr.command_handler(xfr_mode,
@@ -2603,6 +2617,34 @@ class TestXfrin(unittest.TestCase):
self.common_ixfr_setup('refresh', True)
self.assertEqual(RRType.IXFR(), self.xfr.xfrin_started_request_type)
+ def test_command_handler_retransfer_with_tsig(self):
+ self.common_ixfr_setup('retransfer', False, 'example.com.key')
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_retransfer_with_tsig_bad_key(self):
+ # bad keys should not reach xfrin, but should they somehow,
+ # they are ignored (and result in 'key not found' + error log).
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'retransfer', False, 'bad.key')
+
+ def test_command_handler_retransfer_with_tsig_unknown_key(self):
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'retransfer', False, 'no.such.key')
+
+ def test_command_handler_refresh_with_tsig(self):
+ self.common_ixfr_setup('refresh', False, 'example.com.key')
+ self.assertEqual(RRType.AXFR(), self.xfr.xfrin_started_request_type)
+
+ def test_command_handler_refresh_with_tsig_bad_key(self):
+ # bad keys should not reach xfrin, but should they somehow,
+ # they are ignored (and result in 'key not found' + error log).
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'refresh', False, 'bad.key')
+
+ def test_command_handler_refresh_with_tsig_unknown_key(self):
+ self.assertRaises(XfrinZoneInfoException, self.common_ixfr_setup,
+ 'refresh', False, 'no.such.key')
+
def test_command_handler_retransfer_ixfr_disabled(self):
# Similar to the previous case, but explicitly disabled. AXFR should
# be used.
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index e0f6385..10dc249 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -34,9 +34,10 @@ from isc.datasrc import DataSourceClient, ZoneFinder
import isc.net.parse
from isc.xfrin.diff import Diff
from isc.server_common.auth_command import auth_loadzone_command
+from isc.server_common.tsig_keyring import init_keyring, get_keyring
from isc.log_messages.xfrin_messages import *
-isc.log.init("b10-xfrin")
+isc.log.init("b10-xfrin", buffer=True)
logger = isc.log.Logger("xfrin")
# Pending system-wide debug level definitions, the ones we
@@ -71,7 +72,10 @@ AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + "/auth.spec"
AUTH_MODULE_NAME = 'Auth'
XFROUT_MODULE_NAME = 'Xfrout'
+
+# Remote module and identifiers (according to their spec files)
ZONE_MANAGER_MODULE_NAME = 'Zonemgr'
+
REFRESH_FROM_ZONEMGR = 'refresh_from_zonemgr'
# Constants for debug levels.
@@ -1181,7 +1185,7 @@ class ZoneInfo:
self.set_master_port(config_data.get('master_port'))
self.set_zone_class(config_data.get('class'))
- self.set_tsig_key(config_data.get('tsig_key'))
+ self.set_tsig_key_name(config_data.get('tsig_key'))
self.set_use_ixfr(config_data.get('use_ixfr'))
def set_name(self, name_str):
@@ -1242,20 +1246,32 @@ class ZoneInfo:
errmsg = "invalid zone class: " + zone_class_str
raise XfrinZoneInfoException(errmsg)
- def set_tsig_key(self, tsig_key_str):
- """Set the tsig_key for this zone, given a TSIG key string
- representation. If tsig_key_str is None, no TSIG key will
- be set. Raises XfrinZoneInfoException if tsig_key_str cannot
- be parsed."""
+ def set_tsig_key_name(self, tsig_key_str):
+ """Set the name of the tsig_key for this zone. If tsig_key_str
+ is None, no TSIG key will be used. This name is used to
+ find the TSIG key to use for transfers in the global TSIG
+ key ring.
+ Raises XfrinZoneInfoException if tsig_key_str is not a valid
+ (dns) name."""
if tsig_key_str is None:
- self.tsig_key = None
+ self.tsig_key_name = None
else:
+ # can throw a number of exceptions but it is just one
+ # call, so Exception should be OK here
try:
- self.tsig_key = TSIGKey(tsig_key_str)
- except InvalidParameter as ipe:
- logger.error(XFRIN_BAD_TSIG_KEY_STRING, tsig_key_str)
- errmsg = "bad TSIG key string: " + tsig_key_str
- raise XfrinZoneInfoException(errmsg)
+ self.tsig_key_name = Name(tsig_key_str)
+ except Exception as exc:
+ raise XfrinZoneInfoException("Bad TSIG key name: " + str(exc))
+
+ def get_tsig_key(self):
+ if self.tsig_key_name is None:
+ return None
+ result, key = get_keyring().find(self.tsig_key_name)
+ if result != isc.dns.TSIGKeyRing.SUCCESS:
+ raise XfrinZoneInfoException("TSIG key not found in keyring: " +
+ self.tsig_key_name.to_text())
+ else:
+ return key
def set_use_ixfr(self, use_ixfr):
"""Set use_ixfr. If set to True, it will use
@@ -1310,6 +1326,7 @@ class Xfrin:
self.config_handler(config_data)
self._module_cc.add_remote_config(AUTH_SPECFILE_LOCATION,
self._auth_config_handler)
+ init_keyring(self._module_cc)
def _cc_check_command(self):
'''This is a straightforward wrapper for cc.check_command,
@@ -1470,7 +1487,7 @@ class Xfrin:
rrclass,
self._get_db_file(),
master_addr,
- zone_info.tsig_key, request_type,
+ zone_info.get_tsig_key(), request_type,
True)
answer = create_answer(ret[0], ret[1])
else:
@@ -1493,7 +1510,7 @@ class Xfrin:
tsig_key = None
request_type = RRType.AXFR()
if zone_info:
- tsig_key = zone_info.tsig_key
+ tsig_key = zone_info.get_tsig_key()
if zone_info.use_ixfr:
request_type = RRType.IXFR()
db_file = args.get('db_file') or self._get_db_file()
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 554a195..770a8b2 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -24,7 +24,7 @@ The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
"same" (not only for the serial), but it is still not clear what the
receiver should do if this condition does not hold. There was a discussion
-about this at the IETF dnsext wg:
+about this at the IETF dnsext working group:
http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
and the general feeling seems that it would be better to reject the
transfer if a mismatch is detected. On the other hand, also as noted
@@ -43,7 +43,7 @@ The master port as read from the configuration is not a valid port number.
% XFRIN_BAD_TSIG_KEY_STRING bad TSIG key string: %1
The TSIG key string as read from the configuration does not represent
-a valid TSIG key.
+a valid TSIG key. The key is ignored.
% XFRIN_BAD_ZONE_CLASS Invalid zone class: %1
The zone class as read from the configuration is not a valid DNS class.
@@ -61,10 +61,10 @@ There was an error opening a connection to the master. The error is
shown in the log message.
% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
-In an attempt of IXFR processing, the begenning SOA of the first difference
+In an attempt of IXFR processing, the beginning SOA of the first difference
(following the initial SOA that specified the final SOA for all the
differences) was found. This means a connection for xfrin tried IXFR
-and really aot a response for incremental updates.
+and really got a response for incremental updates.
% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
Non incremental transfer was detected at the "first data" of a transfer,
@@ -149,16 +149,23 @@ daemon will now shut down.
The AXFR transfer of the given zone was successful.
The provided information contains the following values:
-messages: Number of overhead DNS messages in the transfer
+messages: Number of overhead DNS messages in the transfer.
records: Number of Resource Records in the full transfer, excluding the
final SOA record that marks the end of the AXFR.
bytes: Full size of the transfer data on the wire.
-run time: Time (in seconds) the complete axfr took
+run time: Time (in seconds) the complete axfr took.
+
+bytes/second: Transfer speed.
-bytes/second: Transfer speed
+% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
+An attempt to start a transfer with TSIG was made, but the configured TSIG
+key name was not found in the TSIG key ring (configuration option
+tsig_keys/keys). The transfer is aborted. The key name that could not be
+found is shown in the log message. Check the configuration and the
+TSIG key ring.
% XFRIN_UNKNOWN_ERROR unknown error: %1
An uncaught exception was raised while running the xfrin daemon. The
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 7506095..1f264e3 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -249,7 +249,7 @@ class TestXfroutSessionBase(unittest.TestCase):
# In the RDATA only the serial matters.
for i in range(0, num_soa):
soa.add_rdata(Rdata(RRType.SOA(), soa_class,
- 'm r ' + str(ixfr) + ' 1 1 1 1'))
+ 'm. r. ' + str(ixfr) + ' 1 1 1 1'))
msg.add_rrset(Message.SECTION_AUTHORITY, soa)
renderer = MessageRenderer()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index b834982..af149d2 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -39,7 +39,7 @@ import isc.server_common.tsig_keyring
from isc.log_messages.xfrout_messages import *
-isc.log.init("b10-xfrout")
+isc.log.init("b10-xfrout", buffer=True)
logger = isc.log.Logger("xfrout")
# Pending system-wide debug level definitions, the ones we
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index ba7ef3c..311a5f9 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -70,7 +70,7 @@ AXFR-style IXFR.
% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
The requested zone in IXFR was not found in the data source
-even though the xfrout daemon sucessfully found the SOA RR of the zone
+even though the xfrout daemon successfully found the SOA RR of the zone
in the data source. This can happen if the administrator removed the
zone from the data source within the small duration between these
operations, but it's more likely to be a bug or broken data source.
@@ -84,9 +84,6 @@ NOTAUTH.
An IXFR request was received, but the client's SOA version is the same as
or newer than that of the server. The xfrout server responds to the
request with the answer section being just one SOA of that version.
-Note: as of this wrting the 'newer version' cannot be identified due to
-the lack of support for the serial number arithmetic. This will soon
-be implemented.
% XFROUT_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
There was a problem in the lower level module handling configuration and
@@ -206,7 +203,7 @@ xfrout daemon process is still running. This xfrout daemon (the one
printing this message) will not start.
% XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
-Pre-response check for an incomding XFR request failed unexpectedly.
+Pre-response check for an incoming XFR request failed unexpectedly.
The most likely cause of this is that some low level error in the data
source, but it may also be other general (more unlikely) errors such
as memory shortage. Some detail of the error is also included in the
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 8cb616d..0412e3f 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -42,7 +42,7 @@ from isc.log_messages.zonemgr_messages import *
from isc.notify import notify_out
# Initialize logging for called modules.
-isc.log.init("b10-zonemgr")
+isc.log.init("b10-zonemgr", buffer=True)
logger = isc.log.Logger("zonemgr")
# Pending system-wide debug level definitions, the ones we
@@ -193,7 +193,8 @@ class ZonemgrRefresh:
def zone_handle_notify(self, zone_name_class, master):
"""Handle zone notify"""
if (self._zone_not_exist(zone_name_class)):
- logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0], zone_name_class[1])
+ logger.error(ZONEMGR_UNKNOWN_ZONE_NOTIFIED, zone_name_class[0],
+ zone_name_class[1], master)
raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
"doesn't belong to zonemgr" % zone_name_class)
self._set_zone_notifier_master(zone_name_class, master)
diff --git a/src/bin/zonemgr/zonemgr_messages.mes b/src/bin/zonemgr/zonemgr_messages.mes
index 88f8dcf..4f58271 100644
--- a/src/bin/zonemgr/zonemgr_messages.mes
+++ b/src/bin/zonemgr/zonemgr_messages.mes
@@ -138,7 +138,7 @@ zone, or, if this error appears without the administrator giving transfer
commands, it can indicate an error in the program, as it should not have
initiated transfers of unknown zones on its own.
-% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1 (class %2) is not known to the zone manager
+% ZONEMGR_UNKNOWN_ZONE_NOTIFIED notified zone %1/%2 from %3 is not known to the zone manager
A NOTIFY was received but the zone that was the subject of the operation
is not being managed by the zone manager. This may indicate an error
in the program (as the operation should not have been initiated if this
diff --git a/src/lib/asiodns/asiodns_messages.mes b/src/lib/asiodns/asiodns_messages.mes
index 8fbafdd..db2902e 100644
--- a/src/lib/asiodns/asiodns_messages.mes
+++ b/src/lib/asiodns/asiodns_messages.mes
@@ -53,6 +53,22 @@ The asynchronous I/O code encountered an error when trying to send data to
the specified address on the given protocol. The number of the system
error that caused the problem is given in the message.
+% ASIODNS_UDP_ASYNC_SEND_FAIL Error sending UDP packet to %1: %2
+The low-level ASIO library reported an error when trying to send a UDP
+packet in asynchronous UDP mode. This can be any error reported by
+send_to(), and can indicate problems such as too high a load on the network,
+or a problem in the underlying library or system.
+This packet is dropped and will not be sent, but service should resume
+normally.
+If you see a single occurrence of this message, it probably does not
+indicate any significant problem, but if it is logged often, it is probably
+a good idea to inspect your network traffic.
+
+% ASIODNS_UDP_SYNC_SEND_FAIL Error sending UDP packet to %1: %2
+The low-level ASIO library reported an error when trying to send a UDP
+packet in synchronous UDP mode. See ASIODNS_UDP_ASYNC_SEND_FAIL for
+more information.
+
% ASIODNS_UNKNOWN_ORIGIN unknown origin for ASIO error code %1 (protocol: %2, address %3)
An internal consistency check on the origin of a message from the
asynchronous I/O module failed. This may indicate an internal error;
diff --git a/src/lib/asiodns/sync_udp_server.cc b/src/lib/asiodns/sync_udp_server.cc
index 0c577f2..95e1c72 100644
--- a/src/lib/asiodns/sync_udp_server.cc
+++ b/src/lib/asiodns/sync_udp_server.cc
@@ -148,9 +148,15 @@ SyncUDPServer::handleRead(const asio::error_code& ec, const size_t length) {
return;
}
+ asio::error_code ec;
socket_->send_to(asio::buffer(output_buffer_->getData(),
output_buffer_->getLength()),
- sender_);
+ sender_, 0, ec);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_UDP_SYNC_SEND_FAIL).
+ arg(sender_.address().to_string()).
+ arg(ec.message());
+ }
}
// And schedule handling another socket.
diff --git a/src/lib/asiodns/udp_server.cc b/src/lib/asiodns/udp_server.cc
index 0f5456b..bdf79a7 100644
--- a/src/lib/asiodns/udp_server.cc
+++ b/src/lib/asiodns/udp_server.cc
@@ -299,10 +299,16 @@ UDPServer::operator()(asio::error_code ec, size_t length) {
// Begin an asynchronous send, and then yield. When the
// send completes, we will resume immediately after this point
// (though we have nothing further to do, so the coroutine
- // will simply exit at that time).
+ // will simply exit at that time, after reporting an error if
+ // there was one).
CORO_YIELD data_->socket_->async_send_to(
buffer(data_->respbuf_->getData(), data_->respbuf_->getLength()),
*data_->sender_, *this);
+ if (ec) {
+ LOG_ERROR(logger, ASIODNS_UDP_ASYNC_SEND_FAIL).
+ arg(data_->sender_->address().to_string()).
+ arg(ec.message());
+ }
}
}
diff --git a/src/lib/asiolink/io_address.cc b/src/lib/asiolink/io_address.cc
index 832452c..90bdf57 100644
--- a/src/lib/asiolink/io_address.cc
+++ b/src/lib/asiolink/io_address.cc
@@ -61,7 +61,7 @@ IOAddress::toText() const {
}
IOAddress
-IOAddress::from_bytes(short family, const uint8_t* data) {
+IOAddress::fromBytes(short family, const uint8_t* data) {
if (data == NULL) {
isc_throw(BadValue, "NULL pointer received.");
} else
@@ -76,6 +76,21 @@ IOAddress::from_bytes(short family, const uint8_t* data) {
return IOAddress(string(addr_str));
}
+std::vector<uint8_t>
+IOAddress::toBytes() const {
+ if (asio_address_.is_v4()) {
+ const asio::ip::address_v4::bytes_type bytes4 =
+ asio_address_.to_v4().to_bytes();
+ return (std::vector<uint8_t>(bytes4.begin(), bytes4.end()));
+ }
+
+ // Not V4 address, so must be a V6 address (else we could never construct
+ // this object).
+ const asio::ip::address_v6::bytes_type bytes6 =
+ asio_address_.to_v6().to_bytes();
+ return (std::vector<uint8_t>(bytes6.begin(), bytes6.end()));
+}
+
short
IOAddress::getFamily() const {
if (asio_address_.is_v4()) {
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 6c18a66..5b11b87 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -24,6 +24,7 @@
#include <functional>
#include <string>
+#include <vector>
#include <exceptions/exceptions.h>
@@ -103,6 +104,19 @@ public:
/// \return AF_INET for IPv4 or AF_INET6 for IPv6.
short getFamily() const;
+ /// \brief Convenience function to check for an IPv4 address
+ ///
+ /// \return true if the address is a V4 address
+ bool isV4() const {
+ return (asio_address_.is_v4());
+ }
+
+ /// \brief Convenience function to check for an IPv6 address
+ ///
+ /// \return true if the address is a V6 address
+ bool isV6() const {
+ return (asio_address_.is_v6());
+ }
/// \brief Creates an address from over wire data.
///
@@ -110,8 +124,13 @@ public:
/// \param data pointer to first char of data
///
/// \return Created IOAddress object
- static IOAddress
- from_bytes(short family, const uint8_t* data);
+ static IOAddress fromBytes(short family, const uint8_t* data);
+
+ /// \brief Return address as set of bytes
+ ///
+ /// \return Contents of the address as a set of bytes in network-byte
+ /// order.
+ std::vector<uint8_t> toBytes() const;
/// \brief Compare addresses for equality
///
diff --git a/src/lib/asiolink/tests/io_address_unittest.cc b/src/lib/asiolink/tests/io_address_unittest.cc
index a1a9076..4bd7626 100644
--- a/src/lib/asiolink/tests/io_address_unittest.cc
+++ b/src/lib/asiolink/tests/io_address_unittest.cc
@@ -18,7 +18,9 @@
#include <asiolink/io_error.h>
#include <asiolink/io_address.h>
+#include <algorithm>
#include <cstring>
+#include <vector>
using namespace isc::asiolink;
@@ -64,7 +66,7 @@ TEST(IOAddressTest, Family) {
EXPECT_EQ(AF_INET6, IOAddress("2001:0DB8:0:0::0012").getFamily());
}
-TEST(IOAddressTest, from_bytes) {
+TEST(IOAddressTest, fromBytes) {
// 2001:db8:1::dead:beef
uint8_t v6[] = {
0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0, 0,
@@ -74,16 +76,55 @@ TEST(IOAddressTest, from_bytes) {
IOAddress addr("::");
EXPECT_NO_THROW({
- addr = IOAddress::from_bytes(AF_INET6, v6);
+ addr = IOAddress::fromBytes(AF_INET6, v6);
});
EXPECT_EQ("2001:db8:1::dead:beef", addr.toText());
EXPECT_NO_THROW({
- addr = IOAddress::from_bytes(AF_INET, v4);
+ addr = IOAddress::fromBytes(AF_INET, v4);
});
EXPECT_EQ(addr.toText(), IOAddress("192.0.2.3").toText());
}
+TEST(IOAddressTest, toBytesV4) {
+ // Address and network byte-order representation of the address.
+ const char* V4STRING = "192.0.2.1";
+ uint8_t V4[] = {0xc0, 0x00, 0x02, 0x01};
+
+ std::vector<uint8_t> actual = IOAddress(V4STRING).toBytes();
+ ASSERT_EQ(sizeof(V4), actual.size());
+ EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V4));
+}
+
+TEST(IOAddressTest, toBytesV6) {
+ // Address and network byte-order representation of the address.
+ const char* V6STRING = "2001:db8:1::dead:beef";
+ uint8_t V6[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef
+ };
+
+ std::vector<uint8_t> actual = IOAddress(V6STRING).toBytes();
+ ASSERT_EQ(sizeof(V6), actual.size());
+ EXPECT_TRUE(std::equal(actual.begin(), actual.end(), V6));
+}
+
+TEST(IOAddressTest, isV4) {
+ const IOAddress address4("192.0.2.1");
+ const IOAddress address6("2001:db8:1::dead:beef");
+
+ EXPECT_TRUE(address4.isV4());
+ EXPECT_FALSE(address6.isV4());
+}
+
+TEST(IOAddressTest, isV6) {
+ const IOAddress address4("192.0.2.1");
+ const IOAddress address6("2001:db8:1::dead:beef");
+
+ EXPECT_FALSE(address4.isV6());
+ EXPECT_TRUE(address6.isV6());
+}
+
TEST(IOAddressTest, uint32) {
IOAddress addr1("192.0.2.5");
diff --git a/src/lib/cc/data.cc b/src/lib/cc/data.cc
index 8d9b87d..4a37087 100644
--- a/src/lib/cc/data.cc
+++ b/src/lib/cc/data.cc
@@ -308,7 +308,7 @@ std::string
str_from_stringstream(std::istream &in, const std::string& file, const int line,
int& pos) throw (JSONError)
{
- char c = 0;
+ char c;
std::stringstream ss;
c = in.get();
++pos;
@@ -390,7 +390,7 @@ number_from_stringstream(std::istream &in, int& pos) {
// value is larger than an int can handle)
ElementPtr
from_stringstream_number(std::istream &in, int &pos) {
- long int i = 0;
+ long int i;
double d = 0.0;
bool is_double = false;
char *endptr;
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index bb84ae2..db25d9f 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -109,8 +109,7 @@ public:
/// \name pure virtuals, every derived class must implement these
- /// \returns true if the other ElementPtr has the same type and
- /// value
+ /// \return true if the other ElementPtr has the same type and value
virtual bool equals(const Element& other) const = 0;
/// Converts the Element to JSON format and appends it to
diff --git a/src/lib/config/config_data.cc b/src/lib/config/config_data.cc
index ebe51cc..fb5dd75 100644
--- a/src/lib/config/config_data.cc
+++ b/src/lib/config/config_data.cc
@@ -235,7 +235,7 @@ ConfigData::getItemList(const std::string& identifier, bool recurse) const {
ConstElementPtr
ConfigData::getFullConfig() const {
ElementPtr result = Element::createMap();
- ConstElementPtr items = getItemList("", true);
+ ConstElementPtr items = getItemList("", false);
BOOST_FOREACH(ConstElementPtr item, items->listValue()) {
result->set(item->stringValue(), getValue(item->stringValue()));
}
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 0bb1bfd..e40600d 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -93,8 +93,8 @@ public:
void setLocalConfig(isc::data::ElementPtr config) { _config = config; }
/// Returns the local (i.e. non-default) configuration.
- /// \returns An ElementPtr pointing to a MapElement containing all
- /// non-default configuration options.
+ /// \return An ElementPtr pointing to a MapElement containing all
+ /// non-default configuration options.
isc::data::ElementPtr getLocalConfig() { return (_config); }
/// Returns a list of all possible configuration options as specified
@@ -110,11 +110,11 @@ public:
isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
bool recurse = false) const;
- /// Returns all current configuration settings (both non-default and default).
+ /// Returns a map of the top-level configuration items, as currently
+ /// set or their defaults
+ ///
/// \return An ElementPtr pointing to a MapElement containing
- /// string->value elements, where the string is the
- /// full identifier of the configuration option and the
- /// value is an ElementPtr with the value.
+ /// the top-level configuration items
isc::data::ConstElementPtr getFullConfig() const;
private:
@@ -126,6 +126,6 @@ private:
}
#endif
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/config/tests/config_data_unittests.cc b/src/lib/config/tests/config_data_unittests.cc
index 26a3fc6..4b83e5c 100644
--- a/src/lib/config/tests/config_data_unittests.cc
+++ b/src/lib/config/tests/config_data_unittests.cc
@@ -118,7 +118,7 @@ TEST(ConfigData, getLocalConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("{ }", cd.getLocalConfig()->str());
-
+
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
EXPECT_EQ("{ \"item1\": 2 }", cd.getLocalConfig()->str());
@@ -141,12 +141,15 @@ TEST(ConfigData, getFullConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
- EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value1\": \"a\" } }", cd.getFullConfig()->str());
+ ElementPtr my_config3 = Element::fromJSON("{ \"item6\": { \"value2\": 123 } }");
+ cd.setLocalConfig(my_config3);
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value2\": 123 } }", cd.getFullConfig()->str());
}
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index f24d62c..dc1007a 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -31,11 +31,15 @@ libb10_datasrc_la_SOURCES += zonetable.h zonetable.cc
libb10_datasrc_la_SOURCES += zone.h zone_finder.cc zone_finder_context.cc
libb10_datasrc_la_SOURCES += result.h
libb10_datasrc_la_SOURCES += logger.h logger.cc
-libb10_datasrc_la_SOURCES += client.h iterator.h
+libb10_datasrc_la_SOURCES += client.h client.cc iterator.h
libb10_datasrc_la_SOURCES += database.h database.cc
libb10_datasrc_la_SOURCES += factory.h factory.cc
libb10_datasrc_la_SOURCES += client_list.h client_list.cc
libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
+libb10_datasrc_la_SOURCES += master_loader_callbacks.h
+libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
+libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
+libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
diff --git a/src/lib/datasrc/client.cc b/src/lib/datasrc/client.cc
new file mode 100644
index 0000000..c69b23c
--- /dev/null
+++ b/src/lib/datasrc/client.cc
@@ -0,0 +1,50 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/client.h>
+
+#include <exceptions/exceptions.h>
+
+/// This file defines a few default implementations for methods.
+///
+/// While some of the detail of the API are worked out, we define
+/// default implementations to ease development for some of the
+/// more tentative methods (those that are not (yet) pure virtual)
+/// They should all throw NotImplemented
+
+namespace isc {
+namespace datasrc {
+
+ZoneIteratorPtr
+DataSourceClient::getIterator(const isc::dns::Name&, bool) const {
+ isc_throw(isc::NotImplemented, "Data source doesn't support iteration");
+}
+
+unsigned int
+DataSourceClient::getZoneCount() const {
+ isc_throw(isc::NotImplemented, "Data source doesn't support getZoneCount");
+}
+
+bool
+DataSourceClient::createZone(const dns::Name&) {
+ isc_throw(isc::NotImplemented, "Data source doesn't support createZone");
+}
+
+bool
+DataSourceClient::deleteZone(const dns::Name&) {
+ isc_throw(isc::NotImplemented, "Data source doesn't support deleteZone");
+}
+
+} // end namespace datasrc
+} // end namespace isc
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 3756a68..607af05 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -20,8 +20,6 @@
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
-#include <exceptions/exceptions.h>
-
#include <datasrc/zone.h>
/// \file
@@ -225,15 +223,7 @@ public:
/// adjusted to the lowest one found.
/// \return Pointer to the iterator.
virtual ZoneIteratorPtr getIterator(const isc::dns::Name& name,
- bool separate_rrs = false) const {
- // This is here to both document the parameter in doxygen (therefore it
- // needs a name) and avoid unused parameter warning.
- static_cast<void>(name);
- static_cast<void>(separate_rrs);
-
- isc_throw(isc::NotImplemented,
- "Data source doesn't support iteration");
- }
+ bool separate_rrs = false) const;
/// Return an updater to make updates to a specific zone.
///
@@ -368,16 +358,68 @@ public:
/// This is an optional convenience method, currently only implemented
/// by the InMemory datasource. By default, it throws NotImplemented
///
+ /// \note This is a tentative API, and this method is likely to change
+ /// or be removed in the near future. For that reason, it currently
+ /// provides a default implementation that throws NotImplemented.
+ ///
/// \exception NotImplemented Thrown if this method is not supported
/// by the datasource
///
- /// \note This is a tentative API, and this method may likely to be
- /// removed in the near future.
/// \return The number of zones known to this datasource
- virtual unsigned int getZoneCount() const {
- isc_throw(isc::NotImplemented,
- "Data source doesn't support getZoneCount");
- }
+ virtual unsigned int getZoneCount() const;
+
+ /// \brief Create a zone in the data source
+ ///
+ /// Creates a new (empty) zone in the data source backend, which
+ /// can subsequently be filled with data (through getUpdater()).
+ ///
+ /// \note This is a tentative API, and this method is likely to change
+ /// or be removed in the near future. For that reason, it currently
+ /// provides a default implementation that throws NotImplemented.
+ ///
+ /// Apart from the two exceptions mentioned below, in theory this
+ /// call can throw anything, depending on the implementation of
+ /// the datasource backend.
+ ///
+ /// \throw NotImplemented If the datasource backend does not support
+ /// direct zone creation.
+ /// \throw DataSourceError If something goes wrong in the data source
+ /// while creating the zone.
+ /// \param zone_name The (fully qualified) name of the zone to create
+ /// \return True if the zone was added, false if it already existed
+ virtual bool createZone(const dns::Name& zone_name);
+
+ /// \brief Delete a zone from the data source
+ ///
+ /// This method also checks if the specified zone exists in the data
+ /// source, and returns true/false depending on whether the zone
+ /// existed/not existed, respectively. In either case, on successful
+ /// return it ensures the data source does not contain the specified
+ /// name of the zone.
+ ///
+ /// \note This is a tentative API, and this method is likely to change
+ /// or be removed in the near future. For that reason, it currently
+ /// provides a default implementation that throws NotImplemented.
+ /// Note also that this method does not delete other database records
+ /// related to the zone, such as zone's resource records or differences
+ /// corresponding to updates made in the zone. This is primarily for
+ /// implementation simplicity (in the currently intended usage there
+ /// wouldn't be such other data at the time of this call anyway) and due
+ /// to the fact that details of managing zones is still in flux. Once
+ /// the design in this area is fixed we may revisit the behavior.
+ ///
+ /// Apart from the two exceptions mentioned below, in theory this
+ /// call can throw anything, depending on the implementation of
+ /// the datasource backend.
+ ///
+ /// \throw NotImplemented If the datasource backend does not support
+ /// direct zone deletion.
+ /// \throw DataSourceError If something goes wrong in the data source
+ /// while deleting the zone.
+ /// \param zone_name The (fully qualified) name of the zone to be deleted
+ /// \return true if the zone previously existed and has been deleted by
+ /// this method; false if the zone didn't exist.
+ virtual bool deleteZone(const dns::Name& zone_name);
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index fbada44..0b010a4 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -19,6 +19,7 @@
#include <datasrc/database.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
+#include <datasrc/rrset_collection_base.h>
#include <exceptions/exceptions.h>
#include <dns/name.h>
@@ -43,6 +44,42 @@ using boost::scoped_ptr;
namespace isc {
namespace datasrc {
+// RAII-style transaction holder; roll back the transaction unless explicitly
+// committed
+namespace {
+class TransactionHolder {
+public:
+ TransactionHolder(DatabaseAccessor& accessor) : accessor_(accessor),
+ committed_(false)
+ {
+ accessor_.startTransaction();
+ }
+ ~TransactionHolder() {
+ if (!committed_) {
+ try {
+ accessor_.rollback();
+ } catch (const DataSourceError& e) {
+ // We generally expect that rollback always succeeds, and
+ // it should in fact succeed in a way we execute it. But
+ // as the public API allows rollback() to fail and
+ // throw, we should expect it. Obviously we cannot re-throw
+ // it. The best we can do is to log it as a critical error.
+ logger.error(DATASRC_DATABASE_TRANSACTION_ROLLBACKFAIL).
+ arg(accessor_.getDBName()).
+ arg(e.what());
+ }
+ }
+ }
+ void commit() {
+ accessor_.commit();
+ committed_ = true;
+ }
+private:
+ DatabaseAccessor& accessor_;
+ bool committed_;
+};
+} // end unnamed namespace
+
DatabaseClient::DatabaseClient(RRClass rrclass,
boost::shared_ptr<DatabaseAccessor>
@@ -80,6 +117,30 @@ DatabaseClient::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
+bool
+DatabaseClient::createZone(const Name& zone_name) {
+ TransactionHolder transaction(*accessor_);
+ const std::pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
+ if (zone.first) {
+ return (false);
+ }
+ accessor_->addZone(zone_name.toText());
+ transaction.commit();
+ return (true);
+}
+
+bool
+DatabaseClient::deleteZone(const Name& zone_name) {
+ TransactionHolder transaction(*accessor_);
+ const std::pair<bool, int> zinfo(accessor_->getZone(zone_name.toText()));
+ if (!zinfo.first) { // if it doesn't exist just return false
+ return (false);
+ }
+ accessor_->deleteZone(zinfo.second);
+ transaction.commit();
+ return (true);
+}
+
DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
int zone_id, const isc::dns::Name& origin) :
accessor_(accessor),
@@ -872,7 +933,7 @@ DatabaseClient::Finder::findOnNameResult(const Name& name,
} else {
LOG_DEBUG(logger, DBG_TRACE_DETAILED,
DATASRC_DATABASE_FOUND_RRSET).
- arg(accessor_->getDBName()).arg(wti->second);
+ arg(accessor_->getDBName()).arg(*wti->second);
}
}
// Found an RR matching the query, so return it. (Note that this
@@ -1324,6 +1385,36 @@ DatabaseClient::getIterator(const isc::dns::Name& name,
return (iterator);
}
+/// \brief datasrc implementation of RRsetCollectionBase.
+class RRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ RRsetCollection(ZoneUpdater& updater, const isc::dns::RRClass& rrclass) :
+ isc::datasrc::RRsetCollectionBase(updater, rrclass)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief A wrapper around \c disable() so that it can be used as a
+ /// public method. \c disable() is protected.
+ void disableWrapper() {
+ disable();
+ }
+
+protected:
+ // TODO: RRsetCollectionBase::Iter is not implemented and the
+ // following two methods just throw.
+
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+};
+
//
// Zone updater using some database system as the underlying data source.
//
@@ -1349,11 +1440,8 @@ public:
logger.info(DATASRC_DATABASE_UPDATER_ROLLBACK)
.arg(zone_name_).arg(zone_class_).arg(db_name_);
} catch (const DataSourceError& e) {
- // We generally expect that rollback always succeeds, and
- // it should in fact succeed in a way we execute it. But
- // as the public API allows rollback() to fail and
- // throw, we should expect it. Obviously we cannot re-throw
- // it. The best we can do is to log it as a critical error.
+ // See The destructor ~TransactionHolder() for the
+ // reason to catch this.
logger.error(DATASRC_DATABASE_UPDATER_ROLLBACKFAIL)
.arg(zone_name_).arg(zone_class_).arg(db_name_)
.arg(e.what());
@@ -1366,6 +1454,15 @@ public:
virtual ZoneFinder& getFinder() { return (*finder_); }
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ if (!rrset_collection_) {
+ // This is only assigned the first time and remains for the
+ // lifetime of the DatabaseUpdater.
+ rrset_collection_.reset(new RRsetCollection(*this, zone_class_));
+ }
+ return (*rrset_collection_);
+ }
+
virtual void addRRset(const AbstractRRset& rrset);
virtual void deleteRRset(const AbstractRRset& rrset);
virtual void commit();
@@ -1390,6 +1487,7 @@ private:
DiffPhase diff_phase_;
Serial serial_;
boost::scoped_ptr<DatabaseClient::Finder> finder_;
+ boost::shared_ptr<isc::datasrc::RRsetCollection> rrset_collection_;
// This is a set of validation checks commonly used for addRRset() and
// deleteRRset to minimize duplicate code logic and to make the main
@@ -1520,6 +1618,14 @@ isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
void
DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
+ if (rrset_collection_) {
+ isc_throw(InvalidOperation,
+ "Cannot add RRset after an RRsetCollection has been "
+ "requested for ZoneUpdater for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+
validateAddOrDelete("add", rrset, DELETE, ADD);
// It's guaranteed rrset has at least one RDATA at this point.
@@ -1570,6 +1676,14 @@ DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
void
DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
+ if (rrset_collection_) {
+ isc_throw(InvalidOperation,
+ "Cannot delete RRset after an RRsetCollection has been "
+ "requested for ZoneUpdater for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+
// If this is the first operation, pretend we are starting a new delete
// sequence after adds. This will simplify the validation below.
if (diff_phase_ == NOT_STARTED) {
@@ -1624,6 +1738,11 @@ DatabaseUpdater::commit() {
accessor_->commit();
committed_ = true; // make sure the destructor won't trigger rollback
+ // Disable the RRsetCollection if it exists.
+ if (rrset_collection_) {
+ rrset_collection_->disableWrapper();
+ }
+
// We release the accessor immediately after commit is completed so that
// we don't hold the possible internal resource any longer.
accessor_.reset();
@@ -1700,7 +1819,7 @@ public:
arg(zone_).arg(rrclass_).arg(accessor_->getDBName());
return (rrset);
} catch (const Exception& ex) {
- LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADR_BADDATA).
+ LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADER_BADDATA).
arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
arg(begin_).arg(end_).arg(ex.what());
isc_throw(DataSourceError, "Failed to create RRset from diff on "
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 320f327..3302adf 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -155,7 +155,7 @@ public:
///
/// It is empty, but needs a virtual one, since we will use the derived
/// classes in polymorphic way.
- virtual ~DatabaseAccessor() { }
+ virtual ~DatabaseAccessor() {}
/// \brief Retrieve a zone identifier
///
@@ -164,8 +164,8 @@ public:
/// apex), as the DatabaseClient will loop trough the labels itself and
/// find the most suitable zone.
///
- /// It is not specified if and what implementation of this method may throw,
- /// so code should expect anything.
+ /// It is not specified if and what implementation of this method may
+ /// throw, so code should expect anything.
///
/// \param name The (fully qualified) domain name of the zone's apex to be
/// looked up.
@@ -177,6 +177,41 @@ public:
/// an opaque handle.
virtual std::pair<bool, int> getZone(const std::string& name) const = 0;
+ /// \brief Add a new zone to the database
+ ///
+ /// This method creates a new (and empty) zone in the database.
+ ///
+ /// Like for addRecordToZone, implementations are not required to
+ /// check for the existence of the given zone name, it is the
+ /// responsibility of the caller to do so.
+ ///
+ /// Callers must also start a transaction before calling this method,
+ /// implementations should throw DataSourceError if this has not been
+ /// done. Callers should also expect DataSourceErrors for other potential
+ /// problems.
+ ///
+ /// \param name The (fully qualified) domain name of the zone to add.
+ /// \return The internal zone id of the zone (whether is existed already
+ /// or was created by this call).
+ virtual int addZone(const std::string& name) = 0;
+
+ /// \brief Delete a zone from the database
+ ///
+ /// Like for deleteRecordToZone, implementations are not required to
+ /// check for the existence of the given zone name, it is the
+ /// responsibility of the caller to do so.
+ ///
+ /// Callers must also start a transaction before calling this method.
+ /// Implementations should throw InvalidOperation if this has not been
+ /// done. Callers should also expect DataSourceError for other potential
+ /// problems specific to the database.
+ ///
+ /// \note This method does not delete other database records related to
+ /// the zone. See \c DataSourceClient::deleteZone for the rationale.
+ ///
+ /// \param zone_id The ID of the zone, that would be returned by getZone().
+ virtual void deleteZone(int zone_id) = 0;
+
/// \brief This holds the internal context of ZoneIterator for databases
///
/// While the ZoneIterator implementation from DatabaseClient does all the
@@ -194,15 +229,15 @@ public:
/// \brief Destructor
///
/// Virtual destructor, so any descendand class is destroyed correctly.
- virtual ~IteratorContext() { }
+ virtual ~IteratorContext() {}
/// \brief Function to provide next resource record
///
/// This function should provide data about the next resource record
/// from the data that is searched. The data is not converted yet.
///
- /// Depending on how the iterator was constructed, there is a difference
- /// in behaviour; for a 'full zone iterator', created with
+ /// Depending on how the iterator was constructed, there is a
+ /// difference in behaviour; for a 'full zone iterator', created with
/// getAllRecords(), all COLUMN_COUNT elements of the array are
/// overwritten.
/// For a 'name iterator', created with getRecords(), the column
@@ -1373,6 +1408,18 @@ public:
/// should use it as a ZoneFinder only.
virtual FindResult findZone(const isc::dns::Name& name) const;
+ /// \brief Create a zone in the database
+ ///
+ /// This method implements \c DataSourceClient::createZone()
+ ///
+ /// It starts a transaction, checks if the zone exists, and if it
+ /// does not, creates it, commits, and returns true. If the zone
+ /// does exist already, it does nothing (except abort the transaction)
+ /// and returns false.
+ virtual bool createZone(const isc::dns::Name& zone_name);
+
+ virtual bool deleteZone(const isc::dns::Name& zone_name);
+
/// \brief Get the zone iterator
///
/// The iterator allows going through the whole zone content. If the
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index 3359d24..6ac9db0 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -70,6 +70,19 @@ The maximum allowed number of items of the hotspot cache is set to the given
number. If there are too many, some of them will be dropped. The size of 0
means no limit.
+% DATASRC_CHECK_ERROR post-load check of zone %1/%2 failed: %3
+The zone was loaded into the data source successfully, but the content fails
+basic sanity checks. See the message if you want to know what exactly is wrong
+with the data. The data can not be used and previous version, if any, will be
+preserved.
+
+% DATASRC_CHECK_WARNING %1/%2: %3
+The zone was loaded into the data source successfully, but there's some problem
+with the content. The problem does not stop the new version from being used
+(though there may be other problems that do, see DATASRC_CHECK_ERROR),
+but it should still be checked and fixed. See the message to know what exactly
+is wrong with the data.
+
% DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
@@ -144,7 +157,7 @@ instead.
% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
The domain name does not have any RRs associated with it, so it doesn't
exist in the database. However, it has a subdomain, so it does exist
-in the DNS address space. This type of domain is known an an "empty
+in the DNS address space. This type of domain is known as an "empty
non-terminal" and so we return NXRRSET instead of NXDOMAIN.
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
@@ -202,7 +215,7 @@ a zone's difference sequences from a database-based data source. The
zone's name and class, database name, and the start and end serials
are shown in the message.
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+% DATASRC_DATABASE_JOURNALREADER_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
This is an error message indicating that a zone's diff is broken and
the data source library failed to convert it to a valid RRset. The
most likely cause of this is that someone has manually modified the
@@ -216,6 +229,16 @@ to find any invalid data and fix it.
No match (not even a wildcard) was found in the named data source for the given
name/type/class in the data source.
+% DATASRC_DATABASE_TRANSACTION_ROLLBACKFAIL failed to roll back transaction on %1: %2
+A transaction on the database was rolled back without committing the
+changes to the database, but the rollback itself unexpectedly fails.
+The higher level implementation does not expect it to fail, so this means
+either a serious operational error in the underlying data source (such as a
+system failure of a database) or software bug in the underlying data source
+implementation. In either case if this message is logged the administrator
+should carefully examine the underlying data source to see what exactly
+happens and whether the data is still valid.
+
% DATASRC_DATABASE_UPDATER_COMMIT updates committed for '%1/%2' on %3
Debug information. A set of updates to a zone has been successfully
committed to the corresponding database backend. The zone name,
@@ -277,7 +300,7 @@ matching the name. This is returned as the result of the search.
The given wildcard matches the name being sough but it as an empty
nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
-namespace but has no RRs assopciated with it). This will produce NXRRSET.
+namespace but has no RRs associated with it). This will produce NXRRSET.
% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
@@ -315,6 +338,18 @@ An error was found in the zone data when it was being loaded from
another data source. The zone was not loaded. The specific error is
shown in the message, and should be addressed.
+% DATASRC_MASTER_LOAD_ERROR %1:%2: Zone '%3/%4' contains error: %5
+There's an error in the given master file. The zone won't be loaded for
+this reason. Parsing might follow, so you might get further errors and
+warnings to fix everything at once. But in case the error is serious enough,
+the parser might just give up or get confused and generate false errors
+afterwards.
+
+% DATASRC_MASTER_LOAD_WARN %1:%2: Zone '%3/%4' has a potential problem: %5
+There's something suspicious in the master file. This is a warning only.
+It may be a problem or it may be harmless, but it should be checked.
+This problem does not stop the zone from being loaded.
+
% DATASRC_MEM_ADD_RRSET adding RRset '%1/%2' into zone '%3'
Debug information. An RRset is being added to the in-memory data source.
@@ -468,7 +503,7 @@ destroyed.
% DATASRC_MEM_WILDCARD_CANCEL wildcard match canceled for '%1'
Debug information. A domain above wildcard was reached, but there's something
below the requested domain. Therefore the wildcard doesn't apply here. This
-behaviour is specified by RFC 1034, section 4.3.3
+behaviour is specified by RFC 1034, section 4.3.3.
% DATASRC_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
The software refuses to load DNAME records into a wildcard domain. It isn't
diff --git a/src/lib/datasrc/master_loader_callbacks.cc b/src/lib/datasrc/master_loader_callbacks.cc
new file mode 100644
index 0000000..04b8940
--- /dev/null
+++ b/src/lib/datasrc/master_loader_callbacks.cc
@@ -0,0 +1,83 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/master_loader_callbacks.h>
+#include <datasrc/zone.h>
+#include <datasrc/logger.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <string>
+#include <boost/bind.hpp>
+
+namespace isc {
+namespace datasrc {
+
+namespace {
+
+void
+logError(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ bool* ok, const std::string& source, size_t line,
+ const std::string& reason)
+{
+ LOG_ERROR(logger, DATASRC_MASTER_LOAD_ERROR).arg(source).arg(line).
+ arg(name).arg(rrclass).arg(reason);
+ if (ok != NULL) {
+ *ok = false;
+ }
+}
+
+void
+logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ const std::string& source, size_t line, const std::string& reason)
+{
+ LOG_WARN(logger, DATASRC_MASTER_LOAD_WARN).arg(source).arg(line).
+ arg(name).arg(rrclass).arg(reason);
+}
+
+void
+addRR(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& type, const isc::dns::RRTTL& ttl,
+ const isc::dns::rdata::RdataPtr& data, ZoneUpdater* updater)
+{
+ // We get description of one RR. The updater takes RRset, so we
+ // wrap it up and push there. It should collate the RRsets of the
+ // same name and type together, since the addRRset should "merge".
+ isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+ rrset.addRdata(data);
+ updater->addRRset(rrset);
+}
+
+}
+
+isc::dns::MasterLoaderCallbacks
+createMasterLoaderCallbacks(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass, bool* ok)
+{
+ return (isc::dns::MasterLoaderCallbacks(boost::bind(&logError, name,
+ rrclass, ok, _1, _2,
+ _3),
+ boost::bind(&logWarning, name,
+ rrclass, _1, _2, _3)));
+}
+
+isc::dns::AddRRCallback
+createMasterLoaderAddCallback(ZoneUpdater& updater) {
+ return (boost::bind(addRR, _1, _2, _3, _4, _5, &updater));
+}
+
+}
+}
diff --git a/src/lib/datasrc/master_loader_callbacks.h b/src/lib/datasrc/master_loader_callbacks.h
new file mode 100644
index 0000000..ae827c9
--- /dev/null
+++ b/src/lib/datasrc/master_loader_callbacks.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MASTER_LOADER_CALLBACKS_H
+#define DATASRC_MASTER_LOADER_CALLBACKS_H
+
+#include <dns/master_loader_callbacks.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+}
+namespace datasrc {
+
+class ZoneUpdater;
+
+/// \brief Create issue callbacks for MasterLoader
+///
+/// This will create set of callbacks for the MasterLoader that
+/// will be used to report any issues found in the zone data.
+///
+/// \param name Name of the zone. Used in logging.
+/// \param rrclass The class of the zone. Used in logging.
+/// \param ok If this is non-NULL and there are any errors during
+/// the loading, it is set to false. Otherwise, it is untouched.
+/// \return Set of callbacks to be passed to the master loader.
+/// \throw std::bad_alloc when allocation fails.
+isc::dns::MasterLoaderCallbacks
+createMasterLoaderCallbacks(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass, bool* ok);
+
+/// \brief Create a callback for MasterLoader to add RRsets.
+///
+/// This creates a callback that can be used by the MasterLoader to add
+/// loaded RRsets into a zone updater.
+///
+/// The zone updater should be opened in the replace mode no changes should
+/// have been done to it yet (but it is not checked). It is not commited
+/// automatically and it is up to the caller to commit the changes (or not).
+/// It must not be destroyed for the whole time of loading.
+///
+/// The function is mostly straightforward packing of the updater.addRRset
+/// into a boost::function, it is defined explicitly due to small technical
+/// annoyences around boost::bind application, so it can be reused.
+///
+/// \param updater The zone updater to use.
+/// \return The callback to be passed to MasterLoader.
+/// \throw std::bad_alloc when allocation fails.
+isc::dns::AddRRCallback
+createMasterLoaderAddCallback(ZoneUpdater& updater);
+
+}
+}
+
+#endif
diff --git a/src/lib/datasrc/memory/Makefile.am b/src/lib/datasrc/memory/Makefile.am
index 7b82269..72b3273 100644
--- a/src/lib/datasrc/memory/Makefile.am
+++ b/src/lib/datasrc/memory/Makefile.am
@@ -27,6 +27,7 @@ libdatasrc_memory_la_SOURCES += memory_client.h memory_client.cc
libdatasrc_memory_la_SOURCES += zone_writer.h
libdatasrc_memory_la_SOURCES += zone_writer_local.h zone_writer_local.cc
libdatasrc_memory_la_SOURCES += load_action.h
+libdatasrc_memory_la_SOURCES += util_internal.h
nodist_libdatasrc_memory_la_SOURCES = memory_messages.h memory_messages.cc
diff --git a/src/lib/datasrc/memory/memory_client.cc b/src/lib/datasrc/memory/memory_client.cc
index 65e1e3b..66e61a2 100644
--- a/src/lib/datasrc/memory/memory_client.cc
+++ b/src/lib/datasrc/memory/memory_client.cc
@@ -209,14 +209,20 @@ private:
RdataIteratorPtr rdata_iterator_;
bool separate_rrs_;
bool ready_;
+ bool examined_rrsigs_;
+ // In case there's nsec3 namespace in the zone, it is represented the same
+ // way as the usual namespace. So we reuse the iterator implementation for
+ // it.
+ ZoneIteratorPtr nsec3_namespace_;
public:
MemoryIterator(const RRClass& rrclass,
- const ZoneTree& tree, const Name& origin,
- bool separate_rrs) :
+ const ZoneTree& tree, const NSEC3Data* nsec3_data,
+ const Name& origin, bool separate_rrs) :
rrclass_(rrclass),
tree_(tree),
separate_rrs_(separate_rrs),
- ready_(true)
+ ready_(true),
+ examined_rrsigs_(false)
{
// Find the first node (origin) and preserve the node chain for future
// searches
@@ -235,10 +241,25 @@ public:
rdata_iterator_ = rrset_->getRdataIterator();
}
}
+
+ // If we have the NSEC3 namespace, get an iterator for it so we can
+ // delegate to it later.
+ if (nsec3_data != NULL) {
+ nsec3_namespace_ =
+ ZoneIteratorPtr(new MemoryIterator(rrclass,
+ nsec3_data->getNSEC3Tree(),
+ NULL, origin,
+ separate_rrs));
+ }
}
virtual ConstRRsetPtr getNextRRset() {
if (!ready_) {
+ // We are done iterating. But in case there's the nsec3 one,
+ // iterate through that one.
+ if (nsec3_namespace_ != ZoneIteratorPtr()) {
+ return (nsec3_namespace_->getNextRRset());
+ }
isc_throw(Unexpected, "Iterating past the zone end");
}
/*
@@ -259,13 +280,19 @@ public:
rrset_.reset(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
rdata_iterator_ = rrset_->getRdataIterator();
+ examined_rrsigs_ = false;
}
}
}
if (node_ == NULL) {
// That's all, folks
ready_ = false;
- return (ConstRRsetPtr());
+ if (nsec3_namespace_ != ZoneIteratorPtr()) {
+ // In case we have the NSEC3 namespace, get one from there.
+ return (nsec3_namespace_->getNextRRset());
+ } else {
+ return (ConstRRsetPtr());
+ }
}
if (separate_rrs_) {
@@ -273,10 +300,24 @@ public:
// 'current' rdata
RRsetPtr result(new RRset(rrset_->getName(),
rrset_->getClass(),
- rrset_->getType(),
+ // If we are looking into the signature,
+ // we need to adjust the type too.
+ examined_rrsigs_ ? RRType::RRSIG() :
+ rrset_->getType(),
rrset_->getTTL()));
result->addRdata(rdata_iterator_->getCurrent());
rdata_iterator_->next();
+ if (!examined_rrsigs_ && rdata_iterator_->isLast()) {
+ // We got to the last RR of the RRset, but we need to look at
+ // the signatures too, if there are any.
+ examined_rrsigs_ = true;
+ const ConstRRsetPtr rrsig = rrset_->getRRsig();
+ if (rrsig != ConstRRsetPtr()) {
+ rrset_ = rrsig;
+ rdata_iterator_ = rrsig->getRdataIterator();
+ } // else - no RRSIG. rdata_iterator_ stays at last, next
+ // condition applies
+ }
if (rdata_iterator_->isLast()) {
// all used up, next.
set_node_ = set_node_->getNext();
@@ -286,6 +327,7 @@ public:
rrset_.reset(new TreeNodeRRset(rrclass_,
node_, set_node_, true));
rdata_iterator_ = rrset_->getRdataIterator();
+ examined_rrsigs_ = false;
}
}
return (result);
@@ -317,7 +359,8 @@ InMemoryClient::getIterator(const Name& name, bool separate_rrs) const {
return (ZoneIteratorPtr(new MemoryIterator(
getClass(),
- result.zone_data->getZoneTree(), name,
+ result.zone_data->getZoneTree(),
+ result.zone_data->getNSEC3Data(), name,
separate_rrs)));
}
diff --git a/src/lib/datasrc/memory/rdataset.cc b/src/lib/datasrc/memory/rdataset.cc
index aae64f3..e7a070f 100644
--- a/src/lib/datasrc/memory/rdataset.cc
+++ b/src/lib/datasrc/memory/rdataset.cc
@@ -122,8 +122,8 @@ RdataSet::create(util::MemorySegment& mem_sgmt, RdataEncoder& encoder,
}
void
-RdataSet::destroy(util::MemorySegment& mem_sgmt, RRClass rrclass,
- RdataSet* rdataset)
+RdataSet::destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+ RRClass rrclass)
{
const size_t data_len =
RdataReader(rrclass, rdataset->type,
diff --git a/src/lib/datasrc/memory/rdataset.h b/src/lib/datasrc/memory/rdataset.h
index b0b3b48..ffa5075 100644
--- a/src/lib/datasrc/memory/rdataset.h
+++ b/src/lib/datasrc/memory/rdataset.h
@@ -187,12 +187,12 @@ public:
///
/// \param mem_sgmt The \c MemorySegment that allocated memory for
/// \c node.
- /// \param rrclass The RR class of the \c RdataSet to be destroyed.
/// \param rdataset A non NULL pointer to a valid \c RdataSet object
+ /// \param rrclass The RR class of the \c RdataSet to be destroyed.
/// that was originally created by the \c create() method (the behavior
/// is undefined if this condition isn't met).
- static void destroy(util::MemorySegment& mem_sgmt, dns::RRClass rrclass,
- RdataSet* rdataset);
+ static void destroy(util::MemorySegment& mem_sgmt, RdataSet* rdataset,
+ dns::RRClass rrclass);
/// \brief Find \c RdataSet of given RR type from a list (const version).
///
@@ -205,6 +205,11 @@ public:
/// if not found in the entire list, it returns NULL. The head pointer
/// can be NULL, in which case this function will simply return NULL.
///
+ /// By default, this method ignores an RdataSet that only contains an
+ /// RRSIG (i.e., missing the covered RdataSet); if the optional
+ /// sigonly_ok parameter is explicitly set to true, it matches such
+ /// RdataSet and returns it if found.
+ ///
/// \note This function is defined as a (static) class method to
/// clarify its an operation for \c RdataSet objects and to make the
/// name shorter. But its implementation does not depend on private
@@ -215,10 +220,14 @@ public:
/// \param rdata_head A pointer to \c RdataSet from which the search
/// starts. It can be NULL.
/// \param type The RRType of \c RdataSet to find.
+ /// \param sigonly_ok Whether it should find an RdataSet that only has
+ /// RRSIG
/// \return A pointer to the found \c RdataSet or NULL if none found.
static const RdataSet*
- find(const RdataSet* rdataset_head, const dns::RRType& type) {
- return (find<const RdataSet>(rdataset_head, type));
+ find(const RdataSet* rdataset_head, const dns::RRType& type,
+ bool sigonly_ok = false)
+ {
+ return (find<const RdataSet>(rdataset_head, type, sigonly_ok));
}
/// \brief Find \c RdataSet of given RR type from a list (non const
@@ -227,8 +236,10 @@ public:
/// This is similar to the const version, except it takes and returns non
/// const pointers.
static RdataSet*
- find(RdataSet* rdataset_head, const dns::RRType& type) {
- return (find<RdataSet>(rdataset_head, type));
+ find(RdataSet* rdataset_head, const dns::RRType& type,
+ bool sigonly_ok = false)
+ {
+ return (find<RdataSet>(rdataset_head, type, sigonly_ok));
}
typedef boost::interprocess::offset_ptr<RdataSet> RdataSetPtr;
@@ -347,12 +358,14 @@ private:
// Shared by both mutable and immutable versions of find()
template <typename RdataSetType>
static RdataSetType*
- find(RdataSetType* rdataset_head, const dns::RRType& type) {
+ find(RdataSetType* rdataset_head, const dns::RRType& type, bool sigonly_ok)
+ {
for (RdataSetType* rdataset = rdataset_head;
rdataset != NULL;
rdataset = rdataset->getNext()) // use getNext() for efficiency
{
- if (rdataset->type == type) {
+ if (rdataset->type == type &&
+ (rdataset->getRdataCount() > 0 || sigonly_ok)) {
return (rdataset);
}
}
diff --git a/src/lib/datasrc/memory/util_internal.h b/src/lib/datasrc/memory/util_internal.h
new file mode 100644
index 0000000..05aaa29
--- /dev/null
+++ b/src/lib/datasrc/memory/util_internal.h
@@ -0,0 +1,57 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_MEMORY_UTIL_INTERNAL_H
+#define DATASRC_MEMORY_UTIL_INTERNAL_H 1
+
+#include <dns/rdataclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+
+namespace isc {
+namespace datasrc {
+namespace memory {
+namespace detail {
+
+/// \brief Return the covered RR type of an RRSIG RRset.
+///
+/// This is a commonly used helper to extract the type covered field of an
+/// RRSIG RRset and return it in the form of an RRType object.
+///
+/// Normally, an empty RRSIG shouldn't be passed to this function, whether
+/// it comes from a master file or another data source iterator, but it could
+/// still happen in some buggy situations. This function catches and rejects
+/// such cases.
+inline dns::RRType
+getCoveredType(const dns::ConstRRsetPtr& sig_rrset) {
+ dns::RdataIteratorPtr it = sig_rrset->getRdataIterator();
+ if (it->isLast()) {
+ isc_throw(isc::Unexpected,
+ "Empty RRset is passed in-memory loader, name: "
+ << sig_rrset->getName());
+ }
+ return (dynamic_cast<const dns::rdata::generic::RRSIG&>(it->getCurrent()).
+ typeCovered());
+}
+
+} // namespace detail
+} // namespace memory
+} // namespace datasrc
+} // namespace isc
+
+#endif // DATASRC_MEMORY_UTIL_INTERNAL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/memory/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index e2cbdef..cc31419 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -49,7 +49,7 @@ rdataSetDeleter(RRClass rrclass, util::MemorySegment* mem_sgmt,
rdataset = rdataset_next)
{
rdataset_next = rdataset->getNext();
- RdataSet::destroy(*mem_sgmt, rrclass, rdataset);
+ RdataSet::destroy(*mem_sgmt, rdataset, rrclass);
}
}
@@ -61,21 +61,28 @@ nullDeleter(RdataSet* rdataset_head) {
NSEC3Data*
NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
const generic::NSEC3PARAM& rdata)
{
- return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+ return (NSEC3Data::create(mem_sgmt, zone_origin,
+ rdata.getHashalg(), rdata.getFlags(),
rdata.getIterations(), rdata.getSalt()));
}
NSEC3Data*
-NSEC3Data::create(util::MemorySegment& mem_sgmt, const generic::NSEC3& rdata) {
- return (NSEC3Data::create(mem_sgmt, rdata.getHashalg(), rdata.getFlags(),
+NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
+ const generic::NSEC3& rdata)
+{
+ return (NSEC3Data::create(mem_sgmt, zone_origin,
+ rdata.getHashalg(), rdata.getFlags(),
rdata.getIterations(), rdata.getSalt()));
}
NSEC3Data*
-NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
- uint8_t flags, uint16_t iterations,
+NSEC3Data::create(util::MemorySegment& mem_sgmt,
+ const Name& zone_origin,
+ uint8_t hashalg, uint8_t flags, uint16_t iterations,
const std::vector<uint8_t>& salt)
{
// NSEC3Data allocation can throw. To avoid leaking the tree, we manage
@@ -87,6 +94,11 @@ NSEC3Data::create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
mem_sgmt, ZoneTree::create(mem_sgmt, true),
boost::bind(nullDeleter, _1));
+ ZoneTree* tree = holder.get();
+ const ZoneTree::Result result =
+ tree->insert(mem_sgmt, zone_origin, NULL);
+ assert(result == ZoneTree::SUCCESS);
+
const size_t salt_len = salt.size();
void* p = mem_sgmt.allocate(sizeof(NSEC3Data) + 1 + salt_len);
diff --git a/src/lib/datasrc/memory/zone_data.h b/src/lib/datasrc/memory/zone_data.h
index 09306d7..974ce24 100644
--- a/src/lib/datasrc/memory/zone_data.h
+++ b/src/lib/datasrc/memory/zone_data.h
@@ -90,9 +90,11 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c NSEC3Data is allocated.
+ /// \param zone_origin The zone origin.
/// \param rdata An NSEC3PARAM RDATA that specifies the NSEC3 parameters
/// to be stored.
static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
const dns::rdata::generic::NSEC3PARAM& rdata);
/// \brief Allocate and construct \c NSEC3Data from NSEC3 Rdata.
@@ -104,9 +106,11 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c NSEC3Data is allocated.
+ /// \param zone_origin The zone origin.
/// \param rdata An NSEC3 RDATA that specifies the NSEC3 parameters
/// to be stored.
static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
const dns::rdata::generic::NSEC3& rdata);
/// \brief Destruct and deallocate \c NSEC3Data.
@@ -193,8 +197,10 @@ public:
private:
// Common subroutine for the public versions of create().
- static NSEC3Data* create(util::MemorySegment& mem_sgmt, uint8_t hashalg,
- uint8_t flags, uint16_t iterations,
+ static NSEC3Data* create(util::MemorySegment& mem_sgmt,
+ const dns::Name& zone_origin,
+ uint8_t hashalg, uint8_t flags,
+ uint16_t iterations,
const std::vector<uint8_t>& salt);
/// \brief The constructor.
@@ -366,9 +372,9 @@ public:
///
/// \param mem_sgmt A \c MemorySegment from which memory for the new
/// \c ZoneData is allocated.
- /// \param name The zone name.
+ /// \param zone_origin The zone origin.
static ZoneData* create(util::MemorySegment& mem_sgmt,
- const dns::Name& zone_name);
+ const dns::Name& zone_origin);
/// \brief Destruct and deallocate \c ZoneData.
///
diff --git a/src/lib/datasrc/memory/zone_data_loader.cc b/src/lib/datasrc/memory/zone_data_loader.cc
index d0a43a3..e224224 100644
--- a/src/lib/datasrc/memory/zone_data_loader.cc
+++ b/src/lib/datasrc/memory/zone_data_loader.cc
@@ -12,14 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <datasrc/master_loader_callbacks.h>
#include <datasrc/memory/zone_data_loader.h>
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/logger.h>
#include <datasrc/memory/segment_object_holder.h>
+#include <datasrc/memory/util_internal.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
#include <dns/rdataclass.h>
#include <dns/rrset.h>
-#include <dns/masterload.h>
#include <boost/foreach.hpp>
#include <boost/bind.hpp>
@@ -35,6 +38,7 @@ namespace datasrc {
namespace memory {
using detail::SegmentObjectHolder;
+using detail::getCoveredType;
namespace { // unnamed namespace
@@ -75,8 +79,6 @@ private:
typedef NodeRRsets::value_type NodeRRsetsVal;
// A helper to identify the covered type of an RRSIG.
- static isc::dns::RRType getCoveredType
- (const isc::dns::ConstRRsetPtr& sig_rrset);
const isc::dns::Name& getCurrentName() const;
private:
@@ -126,34 +128,17 @@ ZoneDataLoader::flushNodeRRsets() {
updater_.add(val.second, sig_rrset);
}
- // Right now, we don't accept RRSIG without covered RRsets (this
- // should eventually allowed, but to do so we'll need to update the
- // finder).
- if (!node_rrsigsets_.empty()) {
- isc_throw(ZoneDataUpdater::AddError,
- "RRSIG is added without covered RRset for "
- << getCurrentName());
+ // Normally rrsigsets map should be empty at this point, but it's still
+ // possible that an RRSIG that don't has covered RRset is added; they
+ // still remain in the map. We add them to the zone separately.
+ BOOST_FOREACH(NodeRRsetsVal val, node_rrsigsets_) {
+ updater_.add(ConstRRsetPtr(), val.second);
}
node_rrsets_.clear();
node_rrsigsets_.clear();
}
-RRType
-ZoneDataLoader::getCoveredType(const ConstRRsetPtr& sig_rrset) {
- RdataIteratorPtr it = sig_rrset->getRdataIterator();
- // Empty RRSIG shouldn't be passed either via a master file or
- // another data source iterator, but it could still happen if the
- // iterator has a bug. We catch and reject such cases.
- if (it->isLast()) {
- isc_throw(isc::Unexpected,
- "Empty RRset is passed in-memory loader, name: "
- << sig_rrset->getName());
- }
- return (dynamic_cast<const generic::RRSIG&>(it->getCurrent()).
- typeCovered());
-}
-
const Name&
ZoneDataLoader::getCurrentName() const {
if (!node_rrsets_.empty()) {
@@ -198,18 +183,25 @@ loadZoneDataInternal(util::MemorySegment& mem_sgmt,
return (holder.release());
}
-// A wrapper for dns::masterLoad used by loadZoneData() below. Essentially it
-// converts the two callback types. Note the mostly redundant wrapper of
+// A wrapper for dns::MasterLoader used by loadZoneData() below. Essentially
+// it converts the two callback types. Note the mostly redundant wrapper of
// boost::bind. It converts function<void(ConstRRsetPtr)> to
-// function<void(RRsetPtr)> (masterLoad() expects the latter). SunStudio
+// function<void(RRsetPtr)> (MasterLoader expects the latter). SunStudio
// doesn't seem to do this conversion if we just pass 'callback'.
void
-masterLoadWrapper(const char* const filename, const Name& origin,
- const RRClass& zone_class, LoadCallback callback)
+masterLoaderWrapper(const char* const filename, const Name& origin,
+ const RRClass& zone_class, LoadCallback callback)
{
+ bool load_ok = false; // (we don't use it)
+ dns::RRCollator collator(boost::bind(callback, _1));
+
try {
- masterLoad(filename, origin, zone_class, boost::bind(callback, _1));
- } catch (MasterLoadError& e) {
+ dns::MasterLoader(filename, origin, zone_class,
+ createMasterLoaderCallbacks(origin, zone_class,
+ &load_ok),
+ collator.getCallback()).load();
+ collator.flush();
+ } catch (const dns::MasterLoaderError& e) {
isc_throw(ZoneLoaderException, e.what());
}
}
@@ -232,7 +224,7 @@ loadZoneData(util::MemorySegment& mem_sgmt,
const std::string& zone_file)
{
return (loadZoneDataInternal(mem_sgmt, rrclass, zone_name,
- boost::bind(masterLoadWrapper,
+ boost::bind(masterLoaderWrapper,
zone_file.c_str(),
zone_name, rrclass,
_1)));
diff --git a/src/lib/datasrc/memory/zone_data_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 3df8c66..51ec03c 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -12,12 +12,18 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/logger.h>
+#include <datasrc/memory/util_internal.h>
#include <datasrc/zone.h>
#include <dns/rdataclass.h>
+#include <cassert>
+#include <string>
+
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -25,6 +31,8 @@ namespace isc {
namespace datasrc {
namespace memory {
+using detail::getCoveredType;
+
void
ZoneDataUpdater::addWildcards(const Name& name) {
Name wname(name);
@@ -99,9 +107,7 @@ ZoneDataUpdater::contextCheck(const AbstractRRset& rrset,
void
ZoneDataUpdater::validate(const isc::dns::ConstRRsetPtr rrset) const {
- if (!rrset) {
- isc_throw(NullRRset, "The rrset provided is NULL");
- }
+ assert(rrset);
if (rrset->getRdataCount() == 0) {
isc_throw(AddError,
@@ -227,7 +233,7 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
if (nsec3_data == NULL) {
- nsec3_data = NSEC3Data::create(mem_sgmt_, nsec3_rdata);
+ nsec3_data = NSEC3Data::create(mem_sgmt_, zone_name_, nsec3_rdata);
zone_data_.setNSEC3Data(nsec3_data);
zone_data_.setSigned(true);
} else {
@@ -241,31 +247,46 @@ ZoneDataUpdater::setupNSEC3(const ConstRRsetPtr rrset) {
}
void
-ZoneDataUpdater::addNSEC3(const ConstRRsetPtr rrset, const ConstRRsetPtr rrsig)
+ZoneDataUpdater::addNSEC3(const Name& name, const ConstRRsetPtr rrset,
+ const ConstRRsetPtr rrsig)
{
- setupNSEC3<generic::NSEC3>(rrset);
+ if (rrset) {
+ setupNSEC3<generic::NSEC3>(rrset);
+ }
NSEC3Data* nsec3_data = zone_data_.getNSEC3Data();
+ if (nsec3_data == NULL) {
+ // This is some tricky case: an RRSIG for NSEC3 is given without the
+ // covered NSEC3, and we don't even know any NSEC3 related data.
+ // This situation is not necessarily broken, but in our current
+ // implementation it's very difficult to deal with. So we reject it;
+ // hopefully this case shouldn't happen in practice, at least unless
+ // zone is really broken.
+ assert(!rrset);
+ isc_throw(NotImplemented,
+ "RRSIG for NSEC3 cannot be added - no known NSEC3 data");
+ }
ZoneNode* node;
- nsec3_data->insertName(mem_sgmt_, rrset->getName(), &node);
+ nsec3_data->insertName(mem_sgmt_, name, &node);
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, rrset, rrsig);
RdataSet* old_rdataset = node->setData(rdataset);
if (old_rdataset != NULL) {
- RdataSet::destroy(mem_sgmt_, rrclass_, old_rdataset);
+ RdataSet::destroy(mem_sgmt_, old_rdataset, rrclass_);
}
}
void
-ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
+ZoneDataUpdater::addRdataSet(const Name& name, const RRType& rrtype,
+ const ConstRRsetPtr rrset,
const ConstRRsetPtr rrsig)
{
- if (rrset->getType() == RRType::NSEC3()) {
- addNSEC3(rrset, rrsig);
+ if (rrtype == RRType::NSEC3()) {
+ addNSEC3(name, rrset, rrsig);
} else {
ZoneNode* node;
- zone_data_.insertName(mem_sgmt_, rrset->getName(), &node);
+ zone_data_.insertName(mem_sgmt_, name, &node);
RdataSet* rdataset_head = node->getData();
@@ -273,13 +294,14 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
// fails and the exception is thrown, it may break strong
// exception guarantee. At the moment we prefer code simplicity
// and don't bother to introduce complicated recovery code.
- contextCheck(*rrset, rdataset_head);
+ if (rrset) { // this check is only for covered RRset, not RRSIG
+ contextCheck(*rrset, rdataset_head);
+ }
- if (RdataSet::find(rdataset_head, rrset->getType()) != NULL) {
+ if (RdataSet::find(rdataset_head, rrtype, true) != NULL) {
isc_throw(AddError,
"RRset of the type already exists: "
- << rrset->getName() << " (type: "
- << rrset->getType() << ")");
+ << name << " (type: " << rrtype << ")");
}
RdataSet* rdataset_new = RdataSet::create(mem_sgmt_, encoder_,
@@ -289,23 +311,25 @@ ZoneDataUpdater::addRdataSet(const ConstRRsetPtr rrset,
// Ok, we just put it in.
+ // Convenient (and more efficient) shortcut to check RRsets at origin
+ const bool is_origin = (node == zone_data_.getOriginNode());
+
// If this RRset creates a zone cut at this node, mark the node
- // indicating the need for callback in find().
- if (rrset->getType() == RRType::NS() &&
- rrset->getName() != zone_name_) {
+ // indicating the need for callback in find(). Note that we do this
+ // only when non RRSIG RRset of that type is added.
+ if (rrset && rrtype == RRType::NS() && !is_origin) {
node->setFlag(ZoneNode::FLAG_CALLBACK);
// If it is DNAME, we have a callback as well here
- } else if (rrset->getType() == RRType::DNAME()) {
+ } else if (rrset && rrtype == RRType::DNAME()) {
node->setFlag(ZoneNode::FLAG_CALLBACK);
}
// If we've added NSEC3PARAM at zone origin, set up NSEC3
// specific data or check consistency with already set up
// parameters.
- if (rrset->getType() == RRType::NSEC3PARAM() &&
- rrset->getName() == zone_name_) {
+ if (rrset && rrtype == RRType::NSEC3PARAM() && is_origin) {
setupNSEC3<generic::NSEC3PARAM>(rrset);
- } else if (rrset->getType() == RRType::NSEC()) {
+ } else if (rrset && rrtype == RRType::NSEC() && is_origin) {
// If it is NSEC signed zone, we mark the zone as signed
// (conceptually "signed" is a broader notion but our
// current zone finder implementation regards "signed" as
@@ -319,27 +343,37 @@ void
ZoneDataUpdater::add(const ConstRRsetPtr& rrset,
const ConstRRsetPtr& sig_rrset)
{
- // Validate input. This will cause an exception to be thrown if the
- // input RRset is empty.
- validate(rrset);
+ // Validate input.
+ if (!rrset && !sig_rrset) {
+ isc_throw(NullRRset,
+ "ZoneDataUpdater::add is given 2 NULL pointers");
+ }
+ if (rrset) {
+ validate(rrset);
+ }
if (sig_rrset) {
validate(sig_rrset);
}
+ const Name& name = rrset ? rrset->getName() : sig_rrset->getName();
+ const RRType& rrtype = rrset ? rrset->getType() :
+ getCoveredType(sig_rrset);
+
// OK, can add the RRset.
- LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).
- arg(rrset->getName()).arg(rrset->getType()).arg(zone_name_);
+ LOG_DEBUG(logger, DBG_TRACE_DATA, DATASRC_MEMORY_MEM_ADD_RRSET).arg(name).
+ arg(rrset ? rrtype.toText() : "RRSIG(" + rrtype.toText() + ")").
+ arg(zone_name_);
// Add wildcards possibly contained in the owner name to the domain
// tree. This can only happen for the normal (non-NSEC3) tree.
// Note: this can throw an exception, breaking strong exception
// guarantee. (see also the note for the call to contextCheck()
// above).
- if (rrset->getType() != RRType::NSEC3()) {
- addWildcards(rrset->getName());
+ if (rrtype != RRType::NSEC3()) {
+ addWildcards(name);
}
- addRdataSet(rrset, sig_rrset);
+ addRdataSet(name, rrtype, rrset, sig_rrset);
}
} // namespace memory
diff --git a/src/lib/datasrc/memory/zone_data_updater.h b/src/lib/datasrc/memory/zone_data_updater.h
index fa9a6af..9d669a0 100644
--- a/src/lib/datasrc/memory/zone_data_updater.h
+++ b/src/lib/datasrc/memory/zone_data_updater.h
@@ -110,10 +110,32 @@ public:
/// populated with the record data and added to the ZoneData for the
/// name in the RRset.
///
- /// This method throws an \c NullRRset exception (see above) if
- /// \c rrset is empty. It throws \c AddError if any of a variety of
- /// validation checks fail for the \c rrset and its associated
- /// \c sig_rrset.
+ /// At least one of \c rrset or \c sig_rrset must be non NULL.
+ /// \c sig_rrset can be reasonably NULL when \c rrset is not signed in
+ /// the zone; it's unusual that \c rrset is NULL, but is still possible
+ /// if these RRsets are given separately to the loader, or if even the
+ /// zone is half broken and really contains an RRSIG that doesn't have
+ /// any covered RRset. This implementation supports these cases (but
+ /// see the note below).
+ ///
+ /// There is one tricky case: Due to a limitation of the current
+ /// implementation, it cannot accept an RRSIG for NSEC3 without the covered
+ /// NSEC3, unless at least one NSEC3 or NSEC3PARAM has been added.
+ /// In this case an isc::NotImplemented exception will be thrown. It
+ /// should be very rare in practice, and hopefully wouldn't be a real
+ /// issue.
+ ///
+ /// \note Due to limitations of the current implementation, if a
+ /// (non RRSIG) RRset and its RRSIG are added separately in different
+ /// calls to this method, the second attempt will be rejected due to
+ /// an \c AddError exception. This will be loosened in Trac
+ /// ticket #2441.
+ ///
+ /// \throw NullRRset Both \c rrset and sig_rrset is NULL
+ /// \throw AddError any of a variety of validation checks fail for the
+ /// \c rrset and its associated \c sig_rrset.
+ /// \throw NotImplemented RRSIG for NSEC3 cannot be added due to internal
+ /// restriction.
///
/// \param rrset The RRset to be added.
/// \param sig_rrset An associated RRSIG RRset for the \c rrset. It
@@ -152,9 +174,12 @@ private:
const isc::dns::NSEC3Hash* getNSEC3Hash();
template <typename T>
void setupNSEC3(const isc::dns::ConstRRsetPtr rrset);
- void addNSEC3(const isc::dns::ConstRRsetPtr rrset,
+ void addNSEC3(const isc::dns::Name& name,
+ const isc::dns::ConstRRsetPtr rrset,
const isc::dns::ConstRRsetPtr rrsig);
- void addRdataSet(const isc::dns::ConstRRsetPtr rrset,
+ void addRdataSet(const isc::dns::Name& name,
+ const isc::dns::RRType& rrtype,
+ const isc::dns::ConstRRsetPtr rrset,
const isc::dns::ConstRRsetPtr rrsig);
util::MemorySegment& mem_sgmt_;
diff --git a/src/lib/datasrc/memory/zone_finder.cc b/src/lib/datasrc/memory/zone_finder.cc
index 11188a0..7f57d8e 100644
--- a/src/lib/datasrc/memory/zone_finder.cc
+++ b/src/lib/datasrc/memory/zone_finder.cc
@@ -216,6 +216,14 @@ createNSEC3RRset(const ZoneNode* node, const RRClass& rrclass) {
assert(rdataset != NULL);
assert(rdataset->type == RRType::NSEC3());
+ // Check for the rare case of RRSIG-only record; in theory it could exist
+ // but we simply consider it broken for NSEC3.
+ if (rdataset->getRdataCount() == 0) {
+ uint8_t labels_buf[LabelSequence::MAX_SERIALIZED_LENGTH];
+ isc_throw(DataSourceError, "Broken zone: RRSIG-only NSEC3 record at "
+ << node->getAbsoluteLabels(labels_buf) << "/" << rrclass);
+ }
+
// Create the RRset. Note the DNSSEC flag: NSEC3 implies DNSSEC.
return (createTreeNodeRRset(node, rdataset, rrclass,
ZoneFinder::FIND_DNSSEC));
@@ -297,8 +305,16 @@ getClosestNSEC(const ZoneData& zone_data,
}
const ZoneNode* prev_node;
- while ((prev_node = zone_data.getZoneTree().previousNode(node_path))
- != NULL) {
+ if (node_path.getLastComparisonResult().getRelation() ==
+ NameComparisonResult::SUBDOMAIN) {
+ // In case the search ended as a sub-domain, the previous node
+ // is already at the top of node_path.
+ prev_node = node_path.getLastComparedNode();
+ } else {
+ prev_node = zone_data.getZoneTree().previousNode(node_path);
+ }
+
+ while (prev_node != NULL) {
if (!prev_node->isEmpty()) {
const RdataSet* found =
RdataSet::find(prev_node->getData(), RRType::NSEC());
@@ -306,6 +322,7 @@ getClosestNSEC(const ZoneData& zone_data,
return (ConstNodeRRset(prev_node, found));
}
}
+ prev_node = zone_data.getZoneTree().previousNode(node_path);
}
// This must be impossible and should be an internal bug.
// See the description at the method declaration.
@@ -627,7 +644,10 @@ private:
// This can be a bit more optimized, but unless we have many
// requested types the effect is probably marginal. For now we
// keep it simple.
- if (std::find(type_beg, type_end, rdset->type) != type_end) {
+ // Check for getRdataCount is necessary not to include RRSIG-only
+ // records accidentally (should be rare, but possible).
+ if (std::find(type_beg, type_end, rdset->type) != type_end &&
+ rdset->getRdataCount() > 0) {
result->push_back(createTreeNodeRRset(node, rdset, rrclass_,
options, real_name));
}
diff --git a/src/lib/datasrc/rrset_collection_base.cc b/src/lib/datasrc/rrset_collection_base.cc
new file mode 100644
index 0000000..b19f62e
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/rrset_collection_base.h>
+#include <datasrc/zone_loader.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+ConstRRsetPtr
+isc::datasrc::RRsetCollectionBase::find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const
+{
+ if (isDisabled()) {
+ isc_throw(RRsetCollectionError, "This RRsetCollection is disabled.");
+ }
+
+ if (rrclass != rrclass_) {
+ // We could throw an exception here, but RRsetCollection is
+ // expected to support an arbitrary collection of RRsets, and it
+ // can be queried just as arbitrarily. So we just return nothing
+ // here.
+ return (ConstRRsetPtr());
+ }
+
+ ZoneFinder& finder = updater_.getFinder();
+ try {
+ ZoneFinderContextPtr result =
+ finder.find(name, rrtype,
+ ZoneFinder::NO_WILDCARD | ZoneFinder::FIND_GLUE_OK);
+ // We return the result rrset only if the result code is
+ // SUCCESS. We return empty if CNAME, DNAME, DELEGATION,
+ // etc. are returned by the ZoneFinder.
+ //
+ // Note that in the case that the queried type itself is CNAME
+ // or DNAME, then the finder will return SUCCESS.
+ if (result->code == ZoneFinder::SUCCESS) {
+ return (result->rrset);
+ } else {
+ return (ConstRRsetPtr());
+ }
+ } catch (const OutOfZone&) {
+ // As RRsetCollection is an arbitrary set of RRsets, in case the
+ // searched name is out of zone, we return nothing instead of
+ // propagating the exception.
+ return (ConstRRsetPtr());
+ } catch (const DataSourceError& e) {
+ isc_throw(RRsetCollectionError,
+ "ZoneFinder threw a DataSourceError: "
+ << e.getMessage().c_str());
+ }
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/rrset_collection_base.h b/src/lib/datasrc/rrset_collection_base.h
new file mode 100644
index 0000000..c02df9a
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_DATASRC_H
+#define RRSET_COLLECTION_DATASRC_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief A forward declaration
+class ZoneUpdater;
+
+/// \brief datasrc derivation of \c isc::dns::RRsetCollectionBase.
+///
+/// This is an abstract class that adds datasrc related detail to
+/// \c isc::dns::RRsetCollectionBase. Derived classes need to complete
+/// the implementation (add iterator support, etc.) before using it.
+class RRsetCollectionBase : public isc::dns::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// No reference (count via \c shared_ptr) to the \c ZoneUpdater is
+ /// acquired. The RRsetCollection must not be used after its
+ /// \c ZoneUpdater has been destroyed.
+ ///
+ /// \param updater The ZoneUpdater to wrap around.
+ /// \param rrclass The RRClass of the records in the zone.
+ RRsetCollectionBase(ZoneUpdater& updater,
+ const isc::dns::RRClass& rrclass) :
+ updater_(updater),
+ rrclass_(rrclass),
+ disabled_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollectionBase() {}
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// Note that not all records added through the updater may
+ /// necessarily be found by this method, such as RRs subject to
+ /// DNAME substitution.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if \c find() results in
+ /// some underlying datasrc error, or if \c disable() was called.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \returns The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+protected:
+ /// \brief Disable the RRsetCollection.
+ ///
+ /// After calling this method, calling operations such as find() or
+ /// using the iterator would result in an \c
+ /// isc::dns::RRsetCollectionError. This method is typically called
+ /// in the \c commit() implementations of some \c ZoneUpdaters.
+ void disable() {
+ disabled_ = true;
+ }
+
+ /// \brief Return if the RRsetCollection is disabled.
+ bool isDisabled() const {
+ return (disabled_);
+ }
+
+ /// \brief See \c isc::dns::RRsetCollectionBase::getBeginning() for
+ /// documentation.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error, or if \c disable() was
+ /// called.
+ virtual IterPtr getBeginning() = 0;
+
+ /// \brief See \c isc::dns::RRsetCollectionBase::getEnd() for
+ /// documentation.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error, or if \c disable() was
+ /// called.
+ virtual IterPtr getEnd() = 0;
+
+private:
+ ZoneUpdater& updater_;
+ isc::dns::RRClass rrclass_;
+ bool disabled_;
+};
+
+/// \brief A pointer-like type pointing to an
+/// \c isc::datasrc::RRsetCollectionBase object.
+///
+/// This type is used to handle RRsetCollections in a polymorphic manner
+/// in libdatasrc.
+typedef boost::shared_ptr<isc::datasrc::RRsetCollectionBase> RRsetCollectionPtr;
+
+} // end of namespace datasrc
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 68d6554..bd71544 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -78,7 +78,9 @@ enum StatementID {
ADD_NSEC3_RECORD = 19,
DEL_ZONE_NSEC3_RECORDS = 20,
DEL_NSEC3_RECORD = 21,
- NUM_STATEMENTS = 22
+ ADD_ZONE = 22,
+ DELETE_ZONE = 23,
+ NUM_STATEMENTS = 24
};
const char* const text_statements[NUM_STATEMENTS] = {
@@ -161,7 +163,12 @@ const char* const text_statements[NUM_STATEMENTS] = {
"DELETE FROM nsec3 WHERE zone_id=?1",
// DEL_NSEC3_RECORD: delete specified NSEC3-related records
"DELETE FROM nsec3 WHERE zone_id=?1 AND hash=?2 "
- "AND rdtype=?3 AND rdata=?4"
+ "AND rdtype=?3 AND rdata=?4",
+
+ // ADD_ZONE: add a zone to the zones table
+ "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)", // ADD_ZONE
+ // DELETE_ZONE: delete a zone from the zones table
+ "DELETE FROM zones WHERE id=?1" // DELETE_ZONE
};
struct SQLite3Parameters {
@@ -612,6 +619,46 @@ SQLite3Accessor::getZone(const std::string& name) const {
return (std::pair<bool, int>(false, 0));
}
+int
+SQLite3Accessor::addZone(const std::string& name) {
+ // Transaction should have been started by the caller
+ if (!dbparameters_->in_transaction) {
+ isc_throw(DataSourceError, "performing addZone on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor proc(*dbparameters_, ADD_ZONE, "add zone");
+ // note: using TRANSIENT here, STATIC would be slightly more
+ // efficient, but TRANSIENT is safer and performance is not
+ // as important in this specific code path.
+ proc.bindText(1, name.c_str(), SQLITE_TRANSIENT);
+ proc.bindText(2, class_.c_str(), SQLITE_TRANSIENT);
+ proc.exec();
+
+ // There are tricks to getting this in one go, but it is safer
+ // to do a new lookup (sqlite3_last_insert_rowid is unsafe
+ // regarding threads and triggers). This requires two
+ // statements, and is unpredictable in the case a zone is added
+ // twice, but this method assumes the caller does not do that
+ // anyway
+ std::pair<bool, int> getzone_result = getZone(name);
+ assert(getzone_result.first);
+ return (getzone_result.second);
+}
+
+void
+SQLite3Accessor::deleteZone(int zone_id) {
+ // Transaction should have been started by the caller
+ if (!dbparameters_->in_transaction) {
+ isc_throw(InvalidOperation, "performing deleteZone on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor proc(*dbparameters_, DELETE_ZONE, "delete zone");
+ proc.bindInt(1, zone_id);
+ proc.exec();
+}
+
namespace {
// Conversion to plain char
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index a8112d4..d014193 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -34,13 +34,11 @@ class RRClass;
namespace datasrc {
-/**
- * \brief Low-level database error
- *
- * This exception is thrown when the SQLite library complains about something.
- * It might mean corrupt database file, invalid request or that something is
- * rotten in the library.
- */
+/// \brief Low-level database error
+///
+/// This exception is thrown when the SQLite library complains about something.
+/// It might mean corrupt database file, invalid request or that something is
+/// rotten in the library.
class SQLite3Error : public DataSourceError {
public:
SQLite3Error(const char* file, size_t line, const char* what) :
@@ -53,24 +51,20 @@ public:
isc::Exception(file, line, what) {}
};
-/**
- * \brief Too Much Data
- *
- * Thrown if a query expecting a certain number of rows back returned too
- * many rows.
- */
+/// \brief Too Much Data
+///
+/// Thrown if a query expecting a certain number of rows back returned too
+/// many rows.
class TooMuchData : public DataSourceError {
public:
TooMuchData(const char* file, size_t line, const char* what) :
DataSourceError(file, line, what) {}
};
-/**
- * \brief Too Little Data
- *
- * Thrown if a query expecting a certain number of rows back returned too
- * few rows (including none).
- */
+/// \brief Too Little Data
+///
+/// Thrown if a query expecting a certain number of rows back returned too
+/// few rows (including none).
class TooLittleData : public DataSourceError {
public:
TooLittleData(const char* file, size_t line, const char* what) :
@@ -79,70 +73,87 @@ public:
struct SQLite3Parameters;
-/**
- * \brief Concrete implementation of DatabaseAccessor for SQLite3 databases
- *
- * This opens one database file with our schema and serves data from there.
- * According to the design, it doesn't interpret the data in any way, it just
- * provides unified access to the DB.
- */
+/// \brief Concrete implementation of DatabaseAccessor for SQLite3 databases
+///
+/// This opens one database file with our schema and serves data from there.
+/// According to the design, it doesn't interpret the data in any way, it just
+/// provides unified access to the DB.
class SQLite3Accessor : public DatabaseAccessor,
public boost::enable_shared_from_this<SQLite3Accessor> {
public:
- /**
- * \brief Constructor
- *
- * This opens the database and becomes ready to serve data from there.
- *
- * \exception SQLite3Error will be thrown if the given database file
- * doesn't work (it is broken, doesn't exist and can't be created, etc).
- *
- * \param filename The database file to be used.
- * \param rrclass Textual representation of RR class ("IN", "CH", etc),
- * specifying which class of data it should serve (while the database
- * file can contain multiple classes of data, a single accessor can
- * work with only one class).
- */
+ /// \brief Constructor
+ ///
+ /// This opens the database and becomes ready to serve data from there.
+ ///
+ /// \exception SQLite3Error will be thrown if the given database file
+ /// doesn't work (it is broken, doesn't exist and can't be created, etc).
+ ///
+ /// \param filename The database file to be used.
+ /// \param rrclass Textual representation of RR class ("IN", "CH", etc),
+ /// specifying which class of data it should serve (while the database
+ /// file can contain multiple classes of data, a single accessor can
+ /// work with only one class).
SQLite3Accessor(const std::string& filename, const std::string& rrclass);
- /**
- * \brief Destructor
- *
- * Closes the database.
- */
- ~SQLite3Accessor();
+ /// \brief Destructor
+ ///
+ /// Closes the database.
+ virtual ~SQLite3Accessor();
/// This implementation internally opens a new sqlite3 database for the
/// same file name specified in the constructor of the original accessor.
virtual boost::shared_ptr<DatabaseAccessor> clone();
- /**
- * \brief Look up a zone
- *
- * This implements the getZone from DatabaseAccessor and looks up a zone
- * in the data. It looks for a zone with the exact given origin and class
- * passed to the constructor.
- *
- * \exception SQLite3Error if something about the database is broken.
- *
- * \param name The (fully qualified) domain name of zone to look up
- * \return The pair contains if the lookup was successful in the first
- * element and the zone id in the second if it was.
- */
+ /// \brief Look up a zone
+ ///
+ /// This implements the getZone from DatabaseAccessor and looks up a zone
+ /// in the data. It looks for a zone with the exact given origin and class
+ /// passed to the constructor.
+ ///
+ /// \exception SQLite3Error if something about the database is broken.
+ ///
+ /// \param name The (fully qualified) domain name of zone to look up
+ /// \return The pair contains if the lookup was successful in the first
+ /// element and the zone id in the second if it was.
virtual std::pair<bool, int> getZone(const std::string& name) const;
- /** \brief Look up all resource records for a name
- *
- * This implements the getRecords() method from DatabaseAccessor
- *
- * \exception SQLite3Error if there is an sqlite3 error when performing
- * the query
- *
- * \param name the name to look up
- * \param id the zone id, as returned by getZone()
- * \param subdomains Match subdomains instead of the name.
- * \return Iterator that contains all records with the given name
- */
+ /// \brief Add a zone
+ ///
+ /// This implements the addZone from DatabaseAccessor and adds a zone
+ /// into the zones table. If the zone exists already, it is still added,
+ /// so the caller should make sure this does not happen (by making
+ /// sure the zone does not exist). In the case of duplicate addition,
+ /// it is undefined which zone id is returned.
+ ///
+ /// The class of the newly created zone is the class passed at construction
+ /// time of the accessor.
+ ///
+ /// This method requires a transaction has been started (with
+ /// \c beginTransaction) by the caller.
+ ///
+ /// \exception DataSourceError if no transaction is active, or if there
+ /// is an SQLite3 error when performing the
+ /// queries.
+ ///
+ /// \param name The origin name of the zone to add
+ /// \return the id of the zone that has been added
+ virtual int addZone(const std::string& name);
+
+ // Nothing special to add for this implementation (the base class
+ // description is sufficient).
+ virtual void deleteZone(int zone_id);
+
+ /// \brief Look up all resource records for a name
+ ///
+ /// This implements the getRecords() method from DatabaseAccessor
+ ///
+ /// \exception SQLite3Error if there is an sqlite3 error when performing
+ /// the query
+ ///
+ /// \param name the name to look up
+ /// \param id the zone id, as returned by getZone()
+ /// \param subdomains Match subdomains instead of the name.
+ /// \return Iterator that contains all records with the given name
virtual IteratorContextPtr getRecords(const std::string& name,
int id,
bool subdomains = false) const;
@@ -155,35 +166,33 @@ public:
virtual IteratorContextPtr getNSEC3Records(const std::string& hash,
int id) const;
- /** \brief Look up all resource records for a zone
- *
- * This implements the getRecords() method from DatabaseAccessor
- *
- * \exception SQLite3Error if there is an sqlite3 error when performing
- * the query
- *
- * \param id the zone id, as returned by getZone()
- * \return Iterator that contains all records in the given zone
- */
+ /// \brief Look up all resource records for a zone
+ ///
+ /// This implements the getRecords() method from DatabaseAccessor
+ ///
+ /// \exception SQLite3Error if there is an sqlite3 error when performing
+ /// the query
+ ///
+ /// \param id the zone id, as returned by getZone()
+ /// \return Iterator that contains all records in the given zone
virtual IteratorContextPtr getAllRecords(int id) const;
- /** \brief Creates an iterator context for a set of differences.
- *
- * Implements the getDiffs() method from DatabaseAccessor
- *
- * \exception NoSuchSerial if either of the versions do not exist in
- * the difference table.
- * \exception SQLite3Error if there is an sqlite3 error when performing
- * the query
- *
- * \param id The ID of the zone, returned from getZone().
- * \param start The SOA serial number of the version of the zone from
- * which the difference sequence should start.
- * \param end The SOA serial number of the version of the zone at which
- * the difference sequence should end.
- *
- * \return Iterator containing difference records.
- */
+ /// \brief Creates an iterator context for a set of differences.
+ ///
+ /// Implements the getDiffs() method from DatabaseAccessor
+ ///
+ /// \exception NoSuchSerial if either of the versions do not exist in
+ /// the difference table.
+ /// \exception SQLite3Error if there is an sqlite3 error when performing
+ /// the query
+ ///
+ /// \param id The ID of the zone, returned from getZone().
+ /// \param start The SOA serial number of the version of the zone from
+ /// which the difference sequence should start.
+ /// \param end The SOA serial number of the version of the zone at which
+ /// the difference sequence should end.
+ ///
+ /// \return Iterator containing difference records.
virtual IteratorContextPtr
getDiffs(int id, uint32_t start, uint32_t end) const;
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index d2049f1..7c61826 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -59,6 +59,8 @@ run_unittests_SOURCES += zonetable_unittest.cc
run_unittests_SOURCES += zone_finder_context_unittest.cc
run_unittests_SOURCES += faked_nsec3.h faked_nsec3.cc
run_unittests_SOURCES += client_list_unittest.cc
+run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += zone_loader_unittest.cc
# We need the actual module implementation in the tests (they are not part
# of libdatasrc)
@@ -93,6 +95,7 @@ endif
EXTRA_DIST = testdata/brokendb.sqlite3
EXTRA_DIST += testdata/contexttest.zone
+EXTRA_DIST += testdata/contexttest-almost-obsolete.zone
EXTRA_DIST += testdata/diffs.sqlite3
EXTRA_DIST += testdata/duplicate_rrset.zone
EXTRA_DIST += testdata/example2.com
@@ -115,3 +118,5 @@ EXTRA_DIST += testdata/new_minor_schema.sqlite3
EXTRA_DIST += testdata/newschema.sqlite3
EXTRA_DIST += testdata/oldschema.sqlite3
EXTRA_DIST += testdata/static.zone
+EXTRA_DIST += testdata/novalidate.zone
+EXTRA_DIST += testdata/checkwarn.zone
diff --git a/src/lib/datasrc/tests/client_unittest.cc b/src/lib/datasrc/tests/client_unittest.cc
index 87ab5e0..915e3b7 100644
--- a/src/lib/datasrc/tests/client_unittest.cc
+++ b/src/lib/datasrc/tests/client_unittest.cc
@@ -60,4 +60,8 @@ TEST_F(ClientTest, defaultGetZoneCount) {
EXPECT_THROW(client_.getZoneCount(), isc::NotImplemented);
}
+TEST_F(ClientTest, defaultCreateZone) {
+ EXPECT_THROW(client_.createZone(Name("example.com.")), isc::NotImplemented);
+}
+
}
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index dda4de2..9f5ade4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -55,6 +55,7 @@ namespace {
// Imaginary zone IDs used in the mock accessor below.
const int READONLY_ZONE_ID = 42;
+const int NEW_ZONE_ID = 420;
const int WRITABLE_ZONE_ID = 4200;
// Commonly used test data
@@ -257,23 +258,45 @@ const char* TEST_NSEC3_RECORDS[][5] = {
*/
class NopAccessor : public DatabaseAccessor {
public:
- NopAccessor() : database_name_("mock_database")
- { }
+ NopAccessor() : database_name_("mock_database") {
+ zones_["example.org."] = READONLY_ZONE_ID;
+ zones_["null.example.org."] = 13;
+ zones_["empty.example.org."] = 0;
+ zones_["bad.example.org."] = -1;
+ }
virtual std::pair<bool, int> getZone(const std::string& name) const {
- if (name == "example.org.") {
- return (std::pair<bool, int>(true, READONLY_ZONE_ID));
- } else if (name == "null.example.org.") {
- return (std::pair<bool, int>(true, 13));
- } else if (name == "empty.example.org.") {
- return (std::pair<bool, int>(true, 0));
- } else if (name == "bad.example.org.") {
- return (std::pair<bool, int>(true, -1));
+ std::map<std::string, int>::const_iterator found = zones_.find(name);
+ if (found != zones_.end()) {
+ return (std::pair<bool, int>(true, found->second));
} else {
return (std::pair<bool, int>(false, 0));
}
}
+ // A simple implementation of addZone.
+ virtual int addZone(const std::string& zone_name) {
+ if (zone_name == "example.com.") {
+ zones_[zone_name] = NEW_ZONE_ID;
+ }
+
+ // for simplicity we assume zone_name is in zones_ at this point
+ return (zones_[zone_name]);
+ }
+
+ // A simple implementation of deleteZone.
+ virtual void deleteZone(int zone_id) {
+ std::map<std::string, int>::iterator it = zones_.begin();
+ std::map<std::string, int>::iterator end = zones_.end();
+ while (it != end) {
+ if (it->second == zone_id) {
+ zones_.erase(it);
+ return;
+ }
+ ++it;
+ }
+ }
+
virtual boost::shared_ptr<DatabaseAccessor> clone() {
// This accessor is stateless, so we can simply return a new instance.
return (boost::shared_ptr<DatabaseAccessor>(new NopAccessor));
@@ -329,9 +352,10 @@ public:
isc_throw(isc::NotImplemented,
"This test database knows nothing about NSEC3 nor order");
}
+
private:
const std::string database_name_;
-
+ std::map<std::string, int> zones_;
};
/**
@@ -432,7 +456,7 @@ public:
// Check any attempt of multiple transactions
if (did_transaction_) {
- isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
+ isc_throw(DataSourceError, "MockAccessor::startTransaction() "
"called multiple times - likely a bug in the test");
}
@@ -441,6 +465,14 @@ public:
did_transaction_ = true;
}
+ // If the test needs multiple calls to startTransaction() and knows it's
+ // safe, it can use this method to disable the safeguard check in
+ // startTransaction(); the test can also use this method by emulating a
+ // lock conflict by setting is_allowed to false.
+ void allowMoreTransaction(bool is_allowed) {
+ did_transaction_ = !is_allowed;
+ }
+
private:
class DomainIterator : public IteratorContext {
public:
@@ -1287,6 +1319,17 @@ public:
}
}
+ // Mock-only; control whether to allow subsequent transaction.
+ void allowMoreTransaction(bool is_allowed) {
+ if (is_mock_) {
+ // Use a separate variable for MockAccessor&; some compilers
+ // would be confused otherwise.
+ MockAccessor& mock_accessor =
+ dynamic_cast<MockAccessor&>(*current_accessor_);
+ mock_accessor.allowMoreTransaction(is_allowed);
+ }
+ }
+
// Some tests only work for MockAccessor. We remember whether our accessor
// is of that type.
bool is_mock_;
@@ -1780,7 +1823,7 @@ doFindAllTestResult(ZoneFinder& finder, const isc::dns::Name& name,
expected_name, result->rrset->getName());
}
-// When asking for an RRset where RRs somehow have different TTLs, it should
+// When asking for an RRset where RRs somehow have different TTLs, it should
// convert to the lowest one.
TEST_F(MockDatabaseClientTest, ttldiff) {
ZoneIteratorPtr it(this->client_->getIterator(Name("example.org")));
@@ -2178,6 +2221,12 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("dname.example.org."));
+ // below.dname.example.org. has an A record
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ isc::dns::Name("dname.example.org."));
doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
@@ -4092,4 +4141,295 @@ TYPED_TEST(DatabaseClientTest, findNSEC3) {
performNSEC3Test(*finder, true);
}
+TYPED_TEST(DatabaseClientTest, createZone) {
+ const Name new_name("example.com");
+ const DataSourceClient::FindResult
+ zone(this->client_->findZone(new_name));
+ ASSERT_EQ(result::NOTFOUND, zone.code);
+
+ // Adding a new zone; it should work and return true
+ ASSERT_TRUE(this->client_->createZone(new_name));
+ const DataSourceClient::FindResult
+ zone2(this->client_->findZone(new_name));
+ ASSERT_EQ(result::SUCCESS, zone2.code);
+ // And the second call should return false since
+ // it already exists
+ this->allowMoreTransaction(true);
+ ASSERT_FALSE(this->client_->createZone(new_name));
+}
+
+TYPED_TEST(DatabaseClientTest, createZoneRollbackOnLocked) {
+ const Name new_name("example.com");
+ isc::datasrc::ZoneUpdaterPtr updater =
+ this->client_->getUpdater(this->zname_, true);
+ this->allowMoreTransaction(false);
+ ASSERT_THROW(this->client_->createZone(new_name), DataSourceError);
+ // createZone started a transaction as well, but since it failed,
+ // it should have been rolled back. Roll back the other one as
+ // well, and the next attempt should succeed
+ updater.reset();
+ this->allowMoreTransaction(true);
+ ASSERT_TRUE(this->client_->createZone(new_name));
+}
+
+TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
+ const Name new_name("example.com");
+ ASSERT_FALSE(this->client_->createZone(this->zname_));
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
+ ASSERT_TRUE(this->client_->createZone(new_name));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZone) {
+ // Check the zone currently exists.
+ EXPECT_EQ(result::SUCCESS, this->client_->findZone(this->zname_).code);
+
+ // Deleting an existing zone; it should work and return true (previously
+ // existed and is now deleted)
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+
+ // Now it's not found by findZone
+ EXPECT_EQ(result::NOTFOUND, this->client_->findZone(this->zname_).code);
+
+ // And the second call should return false since it doesn't exist any more
+ this->allowMoreTransaction(true);
+ EXPECT_FALSE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnLocked) {
+ isc::datasrc::ZoneUpdaterPtr updater =
+ this->client_->getUpdater(this->zname_, true);
+
+ // updater locks the DB so deleteZone() will fail.
+ this->allowMoreTransaction(false);
+ EXPECT_THROW(this->client_->deleteZone(this->zname_), DataSourceError);
+
+ // deleteZone started a transaction as well, but since it failed,
+ // it should have been rolled back. Roll back the other one as
+ // well, and the next attempt should succeed
+ updater.reset();
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnNotFind) {
+ // attempt of deleting non-existent zone. result in false
+ const Name new_name("example.com");
+ EXPECT_FALSE(this->client_->deleteZone(new_name));
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST_CASE(RRsetCollectionTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+ RRsetCollectionTest() :
+ DatabaseClientTest<ACCESSOR_TYPE>(),
+ updater(this->client_->getUpdater(this->zname_, false)),
+ collection(updater->getRRsetCollection())
+ {}
+
+ ZoneUpdaterPtr updater;
+ isc::datasrc::RRsetCollectionBase& collection;
+};
+
+TYPED_TEST(RRsetCollectionTest, find) {
+ // Test the find() that returns ConstRRsetPtr
+ ConstRRsetPtr rrset = this->collection.find(Name("www.example.org."),
+ RRClass::IN(), RRType::A());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = this->collection.find(Name("foo.example.org"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+ RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+ RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = this->collection.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+
+ // Out-of-zone find()s must not throw.
+ rrset = this->collection.find(Name("www.example.com"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // "cname.example.org." with type CNAME should return the CNAME RRset
+ rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::CNAME(), rrset->getType());
+ EXPECT_EQ(Name("cname.example.org"), rrset->getName());
+
+ // "cname.example.org." with type A should return nothing
+ rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // "dname.example.org." with type DNAME should return the DNAME RRset
+ rrset = this->collection.find(Name("dname.example.org"), this->qclass_,
+ RRType::DNAME());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::DNAME(), rrset->getType());
+ EXPECT_EQ(Name("dname.example.org"), rrset->getName());
+
+ // "below.dname.example.org." with type AAAA should return nothing
+ rrset = this->collection.find(Name("below.dname.example.org"),
+ this->qclass_, RRType::AAAA());
+ EXPECT_FALSE(rrset);
+
+ // "below.dname.example.org." with type A does not return the record
+ // (see top of file). See \c isc::datasrc::RRsetCollectionBase::find()
+ // documentation for details.
+ rrset = this->collection.find(Name("below.dname.example.org"),
+ this->qclass_, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // With the FIND_GLUE_OK option passed to ZoneFinder's find(),
+ // searching for "delegation.example.org." with type NS should
+ // return the NS record. Without FIND_GLUE_OK, ZoneFinder's find()
+ // would return DELEGATION and the find() below would return
+ // nothing.
+ rrset = this->collection.find(Name("delegation.example.org"),
+ this->qclass_, RRType::NS());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
+ EXPECT_EQ(Name("delegation.example.org"), rrset->getName());
+
+ // With the NO_WILDCARD option passed to ZoneFinder's find(),
+ // searching for some "foo.wildcard.example.org." would make
+ // ZoneFinder's find() return NXDOMAIN, and the find() below should
+ // return nothing.
+ rrset = this->collection.find(Name("foo.wild.example.org"),
+ this->qclass_, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // Searching directly for "*.wild.example.org." should return the
+ // record.
+ rrset = this->collection.find(Name("*.wild.example.org"),
+ this->qclass_, RRType::A());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(Name("*.wild.example.org"), rrset->getName());
+}
+
+TYPED_TEST(RRsetCollectionTest, iteratorTest) {
+ // Iterators are currently not implemented.
+ EXPECT_THROW(this->collection.begin(), isc::NotImplemented);
+ EXPECT_THROW(this->collection.end(), isc::NotImplemented);
+}
+
+typedef RRsetCollectionTest<MockAccessor> MockRRsetCollectionTest;
+
+TEST_F(MockRRsetCollectionTest, findError) {
+ // A test using the MockAccessor for checking that FindError is
+ // thrown properly if a find attempt using ZoneFinder results in a
+ // DataSourceError.
+ //
+ // The "dsexception.example.org." name is rigged by the MockAccessor
+ // to throw a DataSourceError.
+ EXPECT_THROW({
+ this->collection.find(Name("dsexception.example.org"), this->qclass_,
+ RRType::A());
+ }, RRsetCollectionError);
+}
+
+TYPED_TEST_CASE(RRsetCollectionAndUpdaterTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionAndUpdaterTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+ RRsetCollectionAndUpdaterTest() :
+ DatabaseClientTest<ACCESSOR_TYPE>(),
+ updater_(this->client_->getUpdater(this->zname_, false))
+ {}
+
+ ZoneUpdaterPtr updater_;
+};
+
+// Test that using addRRset() or deleteRRset() on the ZoneUpdater throws
+// after an RRsetCollection is created.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, updateThrows) {
+ // 1. Addition test
+
+ // addRRset() must not throw.
+ this->updater_->addRRset(*this->rrset_);
+
+ // Now setup a new updater and call getRRsetCollection() on it.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ (void) this->updater_->getRRsetCollection();
+
+ // addRRset() must throw isc::InvalidOperation here.
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_),
+ isc::InvalidOperation);
+
+ // 2. Deletion test
+
+ // deleteRRset() must not throw.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // Now setup a new updater and call getRRsetCollection() on it.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+ (void) this->updater_->getRRsetCollection();
+
+ // deleteRRset() must throw isc::InvalidOperation here.
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_),
+ isc::InvalidOperation);
+}
+
+// Test that using an RRsetCollection after calling commit() on the
+// ZoneUpdater throws, as the RRsetCollection is disabled.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, useAfterCommitThrows) {
+ isc::datasrc::RRsetCollectionBase& collection =
+ this->updater_->getRRsetCollection();
+
+ // find() must not throw here.
+ collection.find(Name("foo.wild.example.org"), this->qclass_, RRType::A());
+
+ this->updater_->commit();
+
+ // find() must throw RRsetCollectionError here, as the
+ // RRsetCollection is disabled.
+ EXPECT_THROW(collection.find(Name("foo.wild.example.org"),
+ this->qclass_, RRType::A()),
+ RRsetCollectionError);
+}
+
}
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
new file mode 100644
index 0000000..dc44461
--- /dev/null
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -0,0 +1,151 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/master_loader_callbacks.h>
+#include <datasrc/zone.h>
+
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#include <testutils/dnsmessage_test.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <list>
+#include <string>
+
+using namespace isc::datasrc;
+
+namespace {
+
+// An updater for the tests. Most of the virtual methods throw
+// NotImplemented, as they are not used in the tests.
+class MockUpdater : public ZoneUpdater {
+public:
+ // We do the adding in this test. We currently only check these are
+ // the correct ones, according to a predefined set in a list.
+ virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
+ ASSERT_FALSE(expected_rrsets_.empty());
+
+ // As the rrsetCheck requires a shared pointer, we need to create
+ // a copy.
+ isc::dns::RRsetPtr copy(new isc::dns::BasicRRset(rrset.getName(),
+ rrset.getClass(),
+ rrset.getType(),
+ rrset.getTTL()));
+ EXPECT_FALSE(rrset.getRRsig()) << "Unexpected RRSIG on rrset, not "
+ "copying. Following check will likely fail as a result.";
+ for (isc::dns::RdataIteratorPtr it(rrset.getRdataIterator());
+ !it->isLast(); it->next()) {
+ copy->addRdata(it->getCurrent());
+ }
+
+ isc::testutils::rrsetCheck(expected_rrsets_.front(), copy);
+ // And remove this RRset, as it has been used.
+ expected_rrsets_.pop_front();
+ }
+ // The unused but required methods
+ virtual ZoneFinder& getFinder() {
+ isc_throw(isc::NotImplemented, "Not to be called in this test");
+ }
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ isc_throw(isc::NotImplemented, "Not to be called in this test");
+ }
+ virtual void deleteRRset(const isc::dns::AbstractRRset&) {
+ isc_throw(isc::NotImplemented, "Not to be called in this test");
+ }
+ virtual void commit() {
+ isc_throw(isc::NotImplemented, "Not to be called in this test");
+ }
+ // The RRsets that are expected to appear through addRRset.
+ std::list<isc::dns::RRsetPtr> expected_rrsets_;
+};
+
+class MasterLoaderCallbackTest : public ::testing::Test {
+protected:
+ MasterLoaderCallbackTest() :
+ ok_(true),
+ callbacks_(createMasterLoaderCallbacks(isc::dns::Name("example.org"),
+ isc::dns::RRClass::IN(), &ok_))
+ {}
+ // Generate a new RRset, put it to the updater and return it.
+ void generateRRset(isc::dns::AddRRCallback callback) {
+ const isc::dns::RRsetPtr
+ result(new isc::dns::RRset(isc::dns::Name("example.org"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::A(),
+ isc::dns::RRTTL(3600)));
+ const isc::dns::rdata::RdataPtr
+ data(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
+ isc::dns::RRClass::IN(),
+ "192.0.2.1"));
+
+ result->addRdata(data);
+ updater_.expected_rrsets_.push_back(result);
+
+ callback(result->getName(), result->getClass(), result->getType(),
+ result->getTTL(), data);
+ }
+ // An updater to be passed to the context
+ MockUpdater updater_;
+ // Is the loading OK?
+ bool ok_;
+ // The tested context
+ isc::dns::MasterLoaderCallbacks callbacks_;
+};
+
+// Check it doesn't crash if we don't provide the OK
+TEST_F(MasterLoaderCallbackTest, noOkProvided) {
+ createMasterLoaderCallbacks(isc::dns::Name("example.org"),
+ isc::dns::RRClass::IN(), NULL).
+ error("No source", 1, "No reason");
+}
+
+// Check the callbacks can be called, don't crash and the error one switches
+// to non-OK mode. This, however, does not stop anybody from calling more
+// callbacks.
+TEST_F(MasterLoaderCallbackTest, callbacks) {
+ EXPECT_NO_THROW(callbacks_.warning("No source", 1, "Just for fun"));
+ // The warning does not hurt the OK mode.
+ EXPECT_TRUE(ok_);
+ // Now the error
+ EXPECT_NO_THROW(callbacks_.error("No source", 2, "Some error"));
+ // The OK is turned off once there's at least one error
+ EXPECT_FALSE(ok_);
+
+ // Not being OK does not hurt that much, we can still call the callbacks
+ EXPECT_NO_THROW(callbacks_.warning("No source", 3, "Just for fun"));
+ // The OK is not reset back to true
+ EXPECT_FALSE(ok_);
+ EXPECT_NO_THROW(callbacks_.error("No source", 4, "Some error"));
+}
+
+// Try adding some RRsets.
+TEST_F(MasterLoaderCallbackTest, addRRset) {
+ isc::dns::AddRRCallback
+ callback(createMasterLoaderAddCallback(updater_));
+ // Put some of them in.
+ EXPECT_NO_THROW(generateRRset(callback));
+ EXPECT_NO_THROW(generateRRset(callback));
+ // They all get pushed there right away, so there are none in the queue
+ EXPECT_TRUE(updater_.expected_rrsets_.empty());
+}
+
+}
diff --git a/src/lib/datasrc/tests/memory/Makefile.am b/src/lib/datasrc/tests/memory/Makefile.am
index 67e63b9..a5d73b4 100644
--- a/src/lib/datasrc/tests/memory/Makefile.am
+++ b/src/lib/datasrc/tests/memory/Makefile.am
@@ -32,6 +32,8 @@ run_unittests_SOURCES += ../../tests/faked_nsec3.h ../../tests/faked_nsec3.cc
run_unittests_SOURCES += memory_segment_test.h
run_unittests_SOURCES += segment_object_holder_unittest.cc
run_unittests_SOURCES += memory_client_unittest.cc
+run_unittests_SOURCES += zone_data_loader_unittest.cc
+run_unittests_SOURCES += zone_data_updater_unittest.cc
run_unittests_SOURCES += zone_table_segment_test.h
run_unittests_SOURCES += zone_table_segment_unittest.cc
run_unittests_SOURCES += zone_writer_unittest.cc
diff --git a/src/lib/datasrc/tests/memory/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 38f6eff..0a03645 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -576,16 +576,6 @@ TEST_F(MemoryClientTest, loadDNAMEAndNSNonApex2) {
// Teardown checks for memory segment leaks
}
-TEST_F(MemoryClientTest, loadRRSIGFollowsNothing) {
- // This causes the situation where an RRSIG is added without a covered
- // RRset. Such cases are currently rejected.
- EXPECT_THROW(client_->load(Name("example.org"),
- TEST_DATA_DIR
- "/example.org-rrsig-follows-nothing.zone"),
- ZoneDataUpdater::AddError);
- // Teardown checks for memory segment leaks
-}
-
TEST_F(MemoryClientTest, loadRRSIGs) {
client_->load(Name("example.org"),
TEST_DATA_DIR "/example.org-rrsigs.zone");
@@ -700,6 +690,25 @@ TEST_F(MemoryClientTest, getIteratorSeparateRRs) {
EXPECT_EQ(ConstRRsetPtr(), iterator2->getNextRRset());
}
+// Test we get RRSIGs and NSEC3s too for iterating with separate RRs
+TEST_F(MemoryClientTest, getIteratorSeparateSigned) {
+ client_->load(Name("example.org"),
+ TEST_DATA_DIR "/example.org-nsec3-signed.zone");
+ ZoneIteratorPtr iterator(client_->getIterator(Name("example.org"), true));
+ bool seen_rrsig = false, seen_nsec3 = false;
+ for (ConstRRsetPtr rrset = iterator->getNextRRset();
+ rrset != ConstRRsetPtr(); rrset = iterator->getNextRRset()) {
+ if (rrset->getType() == RRType::RRSIG()) {
+ seen_rrsig = true;
+ } else if (rrset->getType() == RRType::NSEC3()) {
+ seen_nsec3 = true;
+ }
+ }
+
+ EXPECT_TRUE(seen_rrsig);
+ EXPECT_TRUE(seen_nsec3);
+}
+
TEST_F(MemoryClientTest, getIteratorGetSOAThrowsNotImplemented) {
client_->load(Name("example.org"), TEST_DATA_DIR "/example.org-empty.zone");
ZoneIteratorPtr iterator(client_->getIterator(Name("example.org")));
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index 58c6cb1..a45c2bd 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -71,20 +71,20 @@ struct TestRdata {
// unusual and corner cases).
const TestRdata test_rdata_list[] = {
{"IN", "A", "192.0.2.1", 0},
- {"IN", "NS", "ns.example.com", 0},
- {"IN", "CNAME", "cname.example.com", 0},
- {"IN", "SOA", "ns.example.com root.example.com 0 0 0 0 0", 0},
- {"IN", "PTR", "reverse.example.com", 0},
+ {"IN", "NS", "ns.example.com.", 0},
+ {"IN", "CNAME", "cname.example.com.", 0},
+ {"IN", "SOA", "ns.example.com. root.example.com. 0 0 0 0 0", 0},
+ {"IN", "PTR", "reverse.example.com.", 0},
{"IN", "HINFO", "\"cpu-info\" \"OS-info\"", 1},
- {"IN", "MINFO", "root.example.com mbox.example.com", 0},
- {"IN", "MX", "10 mx.example.com", 0},
+ {"IN", "MINFO", "root.example.com. mbox.example.com.", 0},
+ {"IN", "MX", "10 mx.example.com.", 0},
{"IN", "TXT", "\"test1\" \"test 2\"", 1},
- {"IN", "RP", "root.example.com. rp-text.example.com", 0},
- {"IN", "AFSDB", "1 afsdb.example.com", 0},
+ {"IN", "RP", "root.example.com. rp-text.example.com.", 0},
+ {"IN", "AFSDB", "1 afsdb.example.com.", 0},
{"IN", "AAAA", "2001:db8::1", 0},
- {"IN", "SRV", "1 0 10 target.example.com", 0},
- {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com", 1},
- {"IN", "DNAME", "dname.example.com", 0},
+ {"IN", "SRV", "1 0 10 target.example.com.", 0},
+ {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com.", 1},
+ {"IN", "DNAME", "dname.example.com.", 0},
{"IN", "DS", "12892 5 2 5F0EB5C777586DE18DA6B5", 1},
{"IN", "SSHFP", "1 1 dd465c09cfa51fb45020cc83316fff", 1},
// We handle RRSIG separately, so it's excluded from the list
@@ -98,7 +98,7 @@ const TestRdata test_rdata_list[] = {
{"IN", "TYPE65000", "\\# 3 010203", 1}, // some "custom" type
{"IN", "TYPE65535", "\\# 0", 1}, // max RR type, 0-length RDATA
{"CH", "A", "\\# 2 0102", 1}, // A RR for non-IN class; varlen data
- {"CH", "NS", "ns.example.com", 0}, // class CH, generic data
+ {"CH", "NS", "ns.example.com.", 0}, // class CH, generic data
{"CH", "TXT", "BIND10", 1}, // ditto
{"HS", "A", "\\# 5 0102030405", 1}, // A RR for non-IN class; varlen data
{NULL, NULL, NULL, 0}
@@ -409,6 +409,7 @@ public:
reader.nextSig();
reader.rewind();
// Do the actual rendering
+ // cppcheck-suppress unreadVariable
current = &renderer;
reader.iterate();
renderer.writeName(dummyName2());
@@ -484,6 +485,8 @@ public:
current = NULL;
reader.iterateAllSigs();
// Now return the renderer and render the rest of the data
+ // cppcheck-suppress redundantAssignment
+ // cppcheck-suppress unreadVariable
current = &renderer;
reader.iterate();
// Now, this should not break anything and should be valid, but should
diff --git a/src/lib/datasrc/tests/memory/rdataset_unittest.cc b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
index 897e53c..f599999 100644
--- a/src/lib/datasrc/tests/memory/rdataset_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdataset_unittest.cc
@@ -24,6 +24,7 @@
#include <dns/rrtype.h>
#include <dns/rrttl.h>
+#include <datasrc/memory/segment_object_holder.h>
#include <datasrc/memory/rdata_serialization.h>
#include <datasrc/memory/rdataset.h>
@@ -39,6 +40,7 @@ using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc::memory;
using namespace isc::testutils;
+using isc::datasrc::memory::detail::SegmentObjectHolder;
using boost::lexical_cast;
namespace {
@@ -112,7 +114,7 @@ TEST_F(RdataSetTest, create) {
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
ConstRRsetPtr());
checkRdataSet(*rdataset, true, false);
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
}
TEST_F(RdataSetTest, getNext) {
@@ -131,7 +133,62 @@ TEST_F(RdataSetTest, getNext) {
rdataset->next = rdataset;
EXPECT_EQ(rdataset, static_cast<const RdataSet*>(rdataset)->getNext());
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
+}
+
+TEST_F(RdataSetTest, find) {
+ // Create some RdataSets and make a chain of them.
+ SegmentObjectHolder<RdataSet, RRClass> holder1(
+ mem_sgmt_,
+ RdataSet::create(mem_sgmt_, encoder_, a_rrset_, ConstRRsetPtr()),
+ RRClass::IN());
+ ConstRRsetPtr aaaa_rrset =
+ textToRRset("www.example.com. 1076895760 IN AAAA 2001:db8::1");
+ SegmentObjectHolder<RdataSet, RRClass> holder2(
+ mem_sgmt_,
+ RdataSet::create(mem_sgmt_, encoder_, aaaa_rrset, ConstRRsetPtr()),
+ RRClass::IN());
+ ConstRRsetPtr sigonly_rrset =
+ textToRRset("www.example.com. 1076895760 IN RRSIG "
+ "TXT 5 2 3600 20120814220826 20120715220826 "
+ "1234 example.com. FAKE");
+ SegmentObjectHolder<RdataSet, RRClass> holder3(
+ mem_sgmt_,
+ RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(), sigonly_rrset),
+ RRClass::IN());
+
+ RdataSet* rdataset_a = holder1.get();
+ RdataSet* rdataset_aaaa = holder2.get();
+ RdataSet* rdataset_sigonly = holder3.get();
+ RdataSet* rdataset_null = NULL;
+ rdataset_a->next = rdataset_aaaa;
+ rdataset_aaaa->next = rdataset_sigonly;
+
+ // If a non-RRSIG part of rdataset exists for the given type, it will be
+ // returned regardless of the value of sigonly_ok. If it's RRSIG-only
+ // rdataset, it returns non NULL iff sigonly_ok is explicitly set to true.
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA()));
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), true));
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a, RRType::AAAA(), false));
+
+ EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT()));
+ EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a, RRType::TXT(),
+ true));
+ EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a, RRType::TXT(), false));
+
+ // Same tests for the const version of find().
+ const RdataSet* rdataset_a_const = holder1.get();
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA()));
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+ true));
+ EXPECT_EQ(rdataset_aaaa, RdataSet::find(rdataset_a_const, RRType::AAAA(),
+ false));
+
+ EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT()));
+ EXPECT_EQ(rdataset_sigonly, RdataSet::find(rdataset_a_const, RRType::TXT(),
+ true));
+ EXPECT_EQ(rdataset_null, RdataSet::find(rdataset_a_const, RRType::TXT(),
+ false));
}
// A helper function to create an RRset containing the given number of
@@ -154,7 +211,7 @@ TEST_F(RdataSetTest, createManyRRs) {
ConstRRsetPtr());
EXPECT_EQ(8191, rdataset->getRdataCount());
EXPECT_EQ(0, rdataset->getSigRdataCount());
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
// Exceeding that will result in an exception.
EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_,
@@ -173,7 +230,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
rrsig_rrset_);
checkRdataSet(*rdataset, true, true);
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
// Unusual case: TTL doesn't match. This implementation accepts that,
// using the TTL of the covered RRset.
@@ -183,7 +240,7 @@ TEST_F(RdataSetTest, createWithRRSIG) {
"20120715220826 1234 example.com. FAKE"));
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_, rrsig_badttl);
checkRdataSet(*rdataset, true, true);
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
}
// A helper function to create an RRSIG RRset containing the given number of
@@ -218,21 +275,21 @@ TEST_F(RdataSetTest, createManyRRSIGs) {
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
getRRSIGWithRdataCount(7));
EXPECT_EQ(7, rdataset->getSigRdataCount());
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
// 8 would cause overflow in the normal 3-bit field if there were no extra
// count field.
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
getRRSIGWithRdataCount(8));
EXPECT_EQ(8, rdataset->getSigRdataCount());
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
// Up to 2^16-1 RRSIGs are allowed (although that would be useless
// in practice)
rdataset = RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
getRRSIGWithRdataCount(65535));
EXPECT_EQ(65535, rdataset->getSigRdataCount());
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
// Exceeding this limit will result in an exception.
EXPECT_THROW(RdataSet::create(mem_sgmt_, encoder_, a_rrset_,
@@ -250,7 +307,7 @@ TEST_F(RdataSetTest, createWithRRSIGOnly) {
RdataSet* rdataset = RdataSet::create(mem_sgmt_, encoder_, ConstRRsetPtr(),
rrsig_rrset_);
checkRdataSet(*rdataset, false, true);
- RdataSet::destroy(mem_sgmt_, RRClass::IN(), rdataset);
+ RdataSet::destroy(mem_sgmt_, rdataset, RRClass::IN());
}
TEST_F(RdataSetTest, badCeate) {
diff --git a/src/lib/datasrc/tests/memory/testdata/2503-test.zone b/src/lib/datasrc/tests/memory/testdata/2503-test.zone
new file mode 100644
index 0000000..1609dee
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/2503-test.zone
@@ -0,0 +1,13 @@
+example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com. 3600 IN RRSIG SOA 7 2 3600 20130107110921 20121210110921 55148 example.com. 3yVTkwBE98bAcJJHKwk4WL2bKHQqfejzMdcmtteH/A2H07QwexShqQiRhNNyTRj3s1FfmMYlbd67TDZhUnWAwCdMQICj2BS0hzqiZr9iftGsAkIah6LCFx9nRPNIz2CsqXMf9D7rvzKBDUNBIliYGa5h9d7MalAlM7tZAY9YnH0=
+example.com. 3600 IN A 192.0.2.1
+example.com. 3600 IN RRSIG A 7 2 3600 20130107110921 20121210110921 55148 example.com. uCgf94psLo1wWZqAFEcy+5hM1Uh2hAwEVLyiiLESW+jz/cPeOjNFpa2PN1DIUjf2Dj40dDokM3Ev1rSjR6Z+rfHri8rUsahOEw3ZU+UrEDd5cf9MYeo4MlQIrBMAuz3AKOU3YXy7AiPEwIxtlrqrI6o1qGeX8+zKqhjGgnS0/vI=
+example.com. 3600 IN NS example.com.
+example.com. 3600 IN RRSIG NS 7 2 3600 20130107110921 20121210110921 55148 example.com. dDUDANrEdt46e7793dHxl1zOCWm88gLbr3mHYGSbIBfbtEBtISNGL1nwslejyZKnzhYM2kT8bZ3YGe5ibc9wPlGj29nUJgFwqeyIsWJhffhUCaWKYrDWod0WZLJw5uZcS2QHCuQ0+ZMm8dQoL1qswcahmh0VUI7BEKTHk9RJgzc=
+example.com. 3600 IN DNSKEY 256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b}
+example.com. 3600 IN DNSKEY 256 3 7 AwEAAe1s2ycmRzexpaUVjRZAd25ybIv7qP+Rh/tF8zIm2bMGLZl5ByxeyajZpPaGRK2xXWdY4aKBAcDELbOPQ+lLPI4RsiZBY+jY5JuzE5inkpyTtwBFjQl6o3AdBC08BmZjrpWnh9rFx/o7tk0ekU6UblUlhpqxRCDwZPvRSIVurrIr ;{id = 55148 (zsk), size = 1024b}
+example.com. 3600 IN RRSIG DNSKEY 7 2 3600 20130107110921 20121210110921 55148 example.com. FmOXL+p4GIyRA+LEWRjmoSfvWLkimw58dYrO3LS3hS0hRXP7dkhgMmuYFTOlfcfi53R9sQ9DsH6YsZwR5l0rkBE+GDq+MCfYcKXmQ7OLJZjH/3BnJgQtAlktVTfyie+kWOmwPl3l9jfim7Rmr6anXz3fYvy4S06iTsJ9z2weNJU=
+example.com. 3600 IN NSEC3PARAM 1 0 1 -
+example.com. 3600 IN RRSIG NSEC3PARAM 7 2 3600 20130107110921 20121210110921 55148 example.com. zIVekIFafb9uc20ATatQJeSV6QULdvg4VWDAWS4tnQgipPdA6VUggeBXSBaAdZu24wh5ObEdeKMChZOuiHcBYKtveQxdWzYEP0gEVvNXK+jTHpHh+siONJetWdfeCRgVma3F134ckCsS2wDSB9pzjV5lJfDmFa4muaTYZswAM8I=
+9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN NSEC3 1 0 1 - 9vq38lj9qs6s1aruer131mbtsfnvek2p A NS SOA RRSIG DNSKEY NSEC3PARAM
+9vq38lj9qs6s1aruer131mbtsfnvek2p.example.com. 7200 IN RRSIG NSEC3 7 3 7200 20130107110921 20121210110921 55148 example.com. q3j367t58w/NJ41I27dSFvPFQYiAFMl9hh3nwtZCaSc+KxFK+zvWP7Cm21oRDr+4oJCFkmm0kCSFGwarbFHmqJcVuorH6AKtm8Aiy3uQDvRdBZh/P+uBu4gruQSUaT7jVQLi9WdqR25nPtPC8zuxE3Yy4iRZxtZqQ/JVJFlQ/VM=
diff --git a/src/lib/datasrc/tests/memory/testdata/2504-test.zone b/src/lib/datasrc/tests/memory/testdata/2504-test.zone
new file mode 100644
index 0000000..bbbcb83
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/testdata/2504-test.zone
@@ -0,0 +1,10 @@
+example.com. 3600 IN SOA ns.example.com. admin.example.com. 1234 3600 1800 2419200 7200
+example.com. 3600 IN RRSIG SOA 5 2 3600 20130104152459 20121207152459 5196 example.com. ijAvh4ZzAfMCKKWN64aR5CWaHYTAvhJjgBLV+1/RFPG3150DwDr9EI0bCdVyRIMDDLOeQnn0N70rtc051rA2pJVNpEzG2hPIy3Yd2kDsbFBn0atiz3rvjpRLcmmWWYoZihpkFwKRxD41OzJPLg83/yJr1nAu3qSQXh4LmuNfgwI=
+example.com. 3600 IN A 192.0.2.1
+example.com. 3600 IN RRSIG A 5 2 3600 20130104152459 20121207152459 5196 example.com. XnVKYuSH+BANWiULSJAk6wmwnba8hUS9j2cgrCMFcn43XlAUCCNigoMCWhaGbsssMaOyjdwfL3sQZr/NHaQ0ITSL8IZj7t8rOiCvCrUAktuG5UuHmHET6XKPkf03JaoPXIzO9smBqvjFHz1HsuPGxwGh8ztY0p291iXk0zbRwc8=
+example.com. 3600 IN NS example.com.
+example.com. 3600 IN RRSIG NS 5 2 3600 20130104152459 20121207152459 5196 example.com. W5v7dr/WV8FGdxWFS0h1crd1MRxkSrkcmcAs7CT0+uhmVvbx06PsBxGRuCHyL8Y5NimsMs2RLjhkUkJw1+aSLVtTlbC8Pg5dFDK3bGkzBEq3wRcIXf5bM2Lf+l/cWxGY0NgR1Wrq0ckXsnFxegGm9G3OtpCZgTv0L+9cCO4MS7c=
+example.com. 3600 IN DNSKEY 256 3 5 AwEAAZIOhUpUld/OBPeNJ26O1twKj3fRLPt4X8H6N01t4s+VT5v9jaCnCVX4O1LbALdJUv5uPwL4gy4qvf+7Z3Xanp7QCZ5i7ivS1qfiz2tfacXwtVv4aI4EqS7deYN6yD4S/vIpwW+2FoqUWhQtdhC68ex1YfjeEI+CUbAKlF5XgQR5 ;{id = 5196 (zsk), size = 1024b}
+example.com. 3600 IN RRSIG DNSKEY 5 2 3600 20130104152459 20121207152459 5196 example.com. OiN+DBuEDnyEmMe0Qa4MN4SIJ71e+INNhOvoGBpWARiWu83QlPkoJhdU1GADBaanYdFL8UI0os6w2dkwp4aghChD+KWO40NuhUY2LrEUS2jbO3hEcCT3/acaVyucwVv1FjfC4d561Lkfnh8DM9nk5i75IeWVLklMqret1t/f0uY=
+example.com. 7200 IN NSEC example.com. A NS SOA RRSIG NSEC DNSKEY
+example.com. 7200 IN RRSIG NSEC 5 2 7200 20130104152459 20121207152459 5196 example.com. P4eCHTNJImfwQ+wLa3jeySkVeJnzCmb4zFUDQEZIk3GSjLGUHZhHswqSAhpgevx6ZNX/pOqN/Dybf9oadFCZXyoMQDijP02LcDEl4X1ccNiakg6i/9RG9PcEx5ZWJlCFAJ0tOhp1kauZu/HUlkscTAVgBRud0qEclWJacH1k80E=
diff --git a/src/lib/datasrc/tests/memory/testdata/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index dddbf0a..ef5f08b 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -29,3 +29,6 @@ EXTRA_DIST += example.org-rrsigs.zone
EXTRA_DIST += example.org-wildcard-dname.zone
EXTRA_DIST += example.org-wildcard-ns.zone
EXTRA_DIST += example.org-wildcard-nsec3.zone
+
+EXTRA_DIST += 2503-test.zone
+EXTRA_DIST += 2504-test.zone
diff --git a/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
new file mode 100644
index 0000000..c005bf1
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_data_loader_unittest.cc
@@ -0,0 +1,65 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+#include <datasrc/memory/zone_data_loader.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataLoaderTest : public ::testing::Test {
+protected:
+ ZoneDataLoaderTest() : zclass_(RRClass::IN()), zone_data_(NULL) {}
+ void TearDown() {
+ if (zone_data_ != NULL) {
+ ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+ }
+ EXPECT_TRUE(mem_sgmt_.allMemoryDeallocated()); // catch any leak here.
+ }
+ const RRClass zclass_;
+ test::MemorySegmentTest mem_sgmt_;
+ ZoneData* zone_data_;
+};
+
+TEST_F(ZoneDataLoaderTest, loadRRSIGFollowsNothing) {
+ // This causes the situation where an RRSIG is added without a covered
+ // RRset. It will be accepted, and corresponding "sig-only" rdata will
+ // be created.
+ zone_data_ = loadZoneData(mem_sgmt_, zclass_, Name("example.org"),
+ TEST_DATA_DIR
+ "/example.org-rrsig-follows-nothing.zone");
+ ZoneNode* node = NULL;
+ zone_data_->insertName(mem_sgmt_, Name("ns1.example.org"), &node);
+ ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+ const RdataSet* rdset = node->getData();
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ EXPECT_EQ(RRType::A(), rdset->type); // there should be only 1 data here
+ EXPECT_EQ(0, rdset->getRdataCount()); // no RDATA
+ EXPECT_EQ(1, rdset->getSigRdataCount()); // but 1 SIG
+
+ // Teardown checks for memory segment leaks
+}
+
+}
diff --git a/src/lib/datasrc/tests/memory/zone_data_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
index 3c28cec..1605fa2 100644
--- a/src/lib/datasrc/tests/memory/zone_data_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_data_unittest.cc
@@ -85,11 +85,16 @@ protected:
// Shared by both test cases using NSEC3 and NSEC3PARAM Rdata
template <typename RdataType>
void
-checkNSEC3Data(MemorySegmentTest& mem_sgmt, const RdataType& expect_rdata) {
- NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, expect_rdata);
+checkNSEC3Data(MemorySegmentTest& mem_sgmt,
+ const Name& zone_name,
+ const RdataType& expect_rdata)
+{
+ NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt, zone_name,
+ expect_rdata);
- // Internal tree should be created and empty.
- EXPECT_EQ(0, nsec3_data->getNSEC3Tree().getNodeCount());
+ // Internal tree should be created and must contain just the origin
+ // node.
+ EXPECT_EQ(1, nsec3_data->getNSEC3Tree().getNodeCount());
EXPECT_EQ(expect_rdata.getHashalg(), nsec3_data->hashalg);
EXPECT_EQ(expect_rdata.getFlags(), nsec3_data->flags);
@@ -117,18 +122,18 @@ checkFindRdataSet(const ZoneTree& tree, const Name& name, RRType type,
TEST_F(ZoneDataTest, createNSEC3Data) {
// Create an NSEC3Data object from various types of RDATA (of NSEC3PARAM
// and of NSEC3), check if the resulting parameters match.
- checkNSEC3Data(mem_sgmt_, param_rdata_); // one 'usual' form of params
- checkNSEC3Data(mem_sgmt_, param_rdata_nosalt_); // empty salt
- checkNSEC3Data(mem_sgmt_, param_rdata_largesalt_); // max-len salt
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_); // one 'usual' form
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_nosalt_); // empty salt
+ checkNSEC3Data(mem_sgmt_, zname_, param_rdata_largesalt_); // max-len salt
// Same concepts of the tests, using NSEC3 RDATA.
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_);
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_nosalt_);
- checkNSEC3Data(mem_sgmt_, nsec3_rdata_largesalt_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_nosalt_);
+ checkNSEC3Data(mem_sgmt_, zname_, nsec3_rdata_largesalt_);
}
TEST_F(ZoneDataTest, addNSEC3) {
- nsec3_data_ = NSEC3Data::create(mem_sgmt_, param_rdata_);
+ nsec3_data_ = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_);
ZoneNode* node = NULL;
nsec3_data_->insertName(mem_sgmt_, nsec3_rrset_->getName(), &node);
@@ -161,7 +166,8 @@ TEST_F(ZoneDataTest, exceptionSafetyOnCreate) {
// will fail due to bad_alloc. It shouldn't cause memory leak
// (that would be caught in TearDown()).
mem_sgmt_.setThrowCount(2);
- EXPECT_THROW(NSEC3Data::create(mem_sgmt_, param_rdata_), std::bad_alloc);
+ EXPECT_THROW(NSEC3Data::create(mem_sgmt_, zname_, param_rdata_),
+ std::bad_alloc);
// allocate() will throw on the insertion of the origin node.
mem_sgmt_.setThrowCount(2);
@@ -214,7 +220,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) {
// Set a new one. The set method should return NULL. The get method
// should return the new one.
- NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, param_rdata_);
+ NSEC3Data* nsec3_data = NSEC3Data::create(mem_sgmt_, zname_, param_rdata_);
NSEC3Data* old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data);
EXPECT_EQ(static_cast<NSEC3Data*>(NULL), old_nsec3_data);
EXPECT_EQ(nsec3_data, zone_data_->getNSEC3Data());
@@ -222,7 +228,7 @@ TEST_F(ZoneDataTest, getSetNSEC3Data) {
// Replace an existing one with a yet another one.
// We're responsible for destroying the old one.
- NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, nsec3_rdata_);
+ NSEC3Data* nsec3_data2 = NSEC3Data::create(mem_sgmt_, zname_, nsec3_rdata_);
old_nsec3_data = zone_data_->setNSEC3Data(nsec3_data2);
EXPECT_EQ(nsec3_data, old_nsec3_data);
EXPECT_EQ(nsec3_data2, zone_data_->getNSEC3Data());
diff --git a/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
new file mode 100644
index 0000000..63c69c8
--- /dev/null
+++ b/src/lib/datasrc/tests/memory/zone_data_updater_unittest.cc
@@ -0,0 +1,208 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <testutils/dnsmessage_test.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+
+#include <datasrc/memory/rdataset.h>
+#include <datasrc/memory/zone_data.h>
+#include <datasrc/memory/zone_data_updater.h>
+
+#include "memory_segment_test.h"
+
+#include <gtest/gtest.h>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <cassert>
+
+using isc::testutils::textToRRset;
+using namespace isc::dns;
+using namespace isc::datasrc::memory;
+
+namespace {
+
+class ZoneDataUpdaterTest : public ::testing::Test {
+protected:
+ ZoneDataUpdaterTest() :
+ zname_("example.org"), zclass_(RRClass::IN()),
+ zone_data_(ZoneData::create(mem_sgmt_, zname_)),
+ updater_(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_, *zone_data_))
+ {}
+
+ ~ZoneDataUpdaterTest() {
+ if (zone_data_ != NULL) {
+ ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+ }
+ if (!mem_sgmt_.allMemoryDeallocated()) {
+ ADD_FAILURE() << "Memory leak detected";
+ }
+ }
+
+ void clearZoneData() {
+ assert(zone_data_ != NULL);
+ ZoneData::destroy(mem_sgmt_, zone_data_, zclass_);
+ zone_data_ = ZoneData::create(mem_sgmt_, zname_);
+ updater_.reset(new ZoneDataUpdater(mem_sgmt_, zclass_, zname_,
+ *zone_data_));
+ }
+
+ const Name zname_;
+ const RRClass zclass_;
+ test::MemorySegmentTest mem_sgmt_;
+ ZoneData* zone_data_;
+ boost::scoped_ptr<ZoneDataUpdater> updater_;
+};
+
+TEST_F(ZoneDataUpdaterTest, bothNull) {
+ // At least either covered RRset or RRSIG must be non NULL.
+ EXPECT_THROW(updater_->add(ConstRRsetPtr(), ConstRRsetPtr()),
+ ZoneDataUpdater::NullRRset);
+}
+
+ZoneNode*
+getNode(isc::util::MemorySegment& mem_sgmt, const Name& name,
+ ZoneData* zone_data)
+{
+ ZoneNode* node = NULL;
+ zone_data->insertName(mem_sgmt, name, &node);
+ EXPECT_NE(static_cast<ZoneNode*>(NULL), node);
+ return (node);
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigOnly) {
+ // RRSIG that doesn't have covered RRset can be added. The resulting
+ // rdataset won't have "normal" RDATA but sig RDATA.
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "www.example.org. 3600 IN RRSIG A 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ ZoneNode* node = getNode(mem_sgmt_, Name("www.example.org"), zone_data_);
+ const RdataSet* rdset = node->getData();
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ rdset = RdataSet::find(rdset, RRType::A(), true);
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ EXPECT_EQ(0, rdset->getRdataCount());
+ EXPECT_EQ(1, rdset->getSigRdataCount());
+
+ // The RRSIG covering A prohibits an actual A RRset from being added.
+ // This should be loosened in future version, but we check the current
+ // behavior.
+ EXPECT_THROW(updater_->add(
+ textToRRset("www.example.org. 3600 IN A 192.0.2.1"),
+ ConstRRsetPtr()), ZoneDataUpdater::AddError);
+
+ // The special "wildcarding" node mark should be added for the RRSIG-only
+ // case, too.
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "*.wild.example.org. 3600 IN RRSIG A 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ node = getNode(mem_sgmt_, Name("wild.example.org"), zone_data_);
+ EXPECT_TRUE(node->getFlag(ZoneData::WILDCARD_NODE));
+
+ // Simply adding RRSIG covering (delegating NS) shouldn't enable callback
+ // in search.
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "child.example.org. 3600 IN RRSIG NS 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ node = getNode(mem_sgmt_, Name("child.example.org"), zone_data_);
+ EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+ // Same for DNAME
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "dname.example.org. 3600 IN RRSIG DNAME 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ node = getNode(mem_sgmt_, Name("dname.example.org"), zone_data_);
+ EXPECT_FALSE(node->getFlag(ZoneNode::FLAG_CALLBACK));
+
+ // Likewise, RRSIG for NSEC3PARAM alone shouldn't make the zone
+ // "NSEC3-signed".
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ EXPECT_FALSE(zone_data_->isNSEC3Signed());
+
+ // And same for (RRSIG for) NSEC and "is signed".
+ updater_->add(ConstRRsetPtr(), textToRRset(
+ "example.org. 3600 IN RRSIG NSEC 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ EXPECT_FALSE(zone_data_->isSigned());
+}
+
+// Commonly used checks for rrsigForNSEC3Only
+void
+checkNSEC3Rdata(isc::util::MemorySegment& mem_sgmt, const Name& name,
+ ZoneData* zone_data)
+{
+ ZoneNode* node = NULL;
+ zone_data->getNSEC3Data()->insertName(mem_sgmt, name, &node);
+ ASSERT_NE(static_cast<ZoneNode*>(NULL), node);
+ const RdataSet* rdset = node->getData();
+ ASSERT_NE(static_cast<RdataSet*>(NULL), rdset);
+ ASSERT_EQ(RRType::NSEC3(), rdset->type);
+ EXPECT_EQ(0, rdset->getRdataCount());
+ EXPECT_EQ(1, rdset->getSigRdataCount());
+}
+
+TEST_F(ZoneDataUpdaterTest, rrsigForNSEC3Only) {
+ // Adding only RRSIG covering NSEC3 is tricky. It should go to the
+ // separate NSEC3 tree, but the separate space is only created when
+ // NSEC3 or NSEC3PARAM is added. So, in many cases RRSIG-only is allowed,
+ // but if no NSEC3 or NSEC3PARAM has been added it will be rejected.
+
+ // Below we use abnormal owner names and RDATA for NSEC3s for brevity,
+ // but that doesn't matter for this test.
+
+ // Add NSEC3PARAM, then RRSIG-only, which is okay.
+ updater_->add(textToRRset(
+ "example.org. 3600 IN NSEC3PARAM 1 0 12 AABBCCDD"),
+ textToRRset(
+ "example.org. 3600 IN RRSIG NSEC3PARAM 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ EXPECT_TRUE(zone_data_->isNSEC3Signed());
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+ // Clear the current content of zone, then add NSEC3
+ clearZoneData();
+ updater_->add(textToRRset(
+ "AABB.example.org. 3600 IN NSEC3 1 0 10 AA 00000000 A"),
+ textToRRset(
+ "AABB.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ updater_->add(ConstRRsetPtr(),
+ textToRRset(
+ "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE"));
+ checkNSEC3Rdata(mem_sgmt_, Name("09GM.example.org"), zone_data_);
+
+ // If we add only RRSIG without any NSEC3 related data beforehand,
+ // it will be rejected; it's a limitation of the current implementation.
+ clearZoneData();
+ EXPECT_THROW(updater_->add(
+ ConstRRsetPtr(),
+ textToRRset(
+ "09GM.example.org. 3600 IN RRSIG NSEC3 5 3 3600 "
+ "20150420235959 20051021000000 1 example.org. FAKE")),
+ isc::NotImplemented);
+}
+
+}
diff --git a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index 4cd08c0..42667da 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include "memory_segment_test.h"
+#include "zone_table_segment_test.h"
// NOTE: this faked_nsec3 inclusion (and all related code below)
// was ported during #2109 for the convenience of implementing #2218
@@ -25,7 +26,10 @@
#include <datasrc/memory/zone_finder.h>
#include <datasrc/memory/zone_data_updater.h>
#include <datasrc/memory/rdata_serialization.h>
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/memory_client.h>
#include <datasrc/data_source.h>
+#include <datasrc/client.h>
#include <testutils/dnsmessage_test.h>
#include <boost/foreach.hpp>
@@ -1358,6 +1362,95 @@ TEST_F(InMemoryZoneFinderTest, findNSEC3ForBadZone) {
DataSourceError);
}
+TEST_F(InMemoryZoneFinderTest, findOrphanRRSIG) {
+ // Make the zone "NSEC signed"
+ addToZoneData(rr_nsec_);
+ const ZoneFinder::FindResultFlags expected_flags =
+ ZoneFinder::RESULT_NSEC_SIGNED;
+
+ // Add A for ns.example.org, and RRSIG-only covering TXT for the same name.
+ // query for the TXT should result in NXRRSET.
+ addToZoneData(rr_ns_a_);
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG TXT 5 3 300 20120814220826 "
+ "20120715220826 1234 example.com. FAKE"));
+ findTest(Name("ns.example.org"), RRType::TXT(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+ // Add RRSIG-only covering NSEC. This shouldn't be returned when NSEC is
+ // requested, whether it's for NXRRSET or NXDOMAIN
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ "ns.example.org. 300 IN RRSIG NSEC 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
+ // The added RRSIG for NSEC could be used for NXRRSET but shouldn't
+ findTest(Name("ns.example.org"), RRType::TXT(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(),
+ expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+ // The added RRSIG for NSEC could be used for NXDOMAIN but shouldn't
+ findTest(Name("nz.example.org"), RRType::A(),
+ ZoneFinder::NXDOMAIN, true, rr_nsec_,
+ expected_flags, NULL, ZoneFinder::FIND_DNSSEC);
+
+ // RRSIG-only CNAME shouldn't be accidentally confused with real CNAME.
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ "nocname.example.org. 300 IN RRSIG CNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
+ findTest(Name("nocname.example.org"), RRType::A(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+
+ // RRSIG-only for NS wouldn't invoke delegation anyway, but we check this
+ // case explicitly.
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ "nodelegation.example.org. 300 IN RRSIG NS 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
+ findTest(Name("nodelegation.example.org"), RRType::A(),
+ ZoneFinder::NXRRSET, true, ConstRRsetPtr(), expected_flags);
+ findTest(Name("www.nodelegation.example.org"), RRType::A(),
+ ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+
+ // Same for RRSIG-only for DNAME
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ "nodname.example.org. 300 IN RRSIG DNAME 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
+ findTest(Name("www.nodname.example.org"), RRType::A(),
+ ZoneFinder::NXDOMAIN, true, ConstRRsetPtr(), expected_flags);
+ // If we have a delegation NS at this node, it will be a bit trickier,
+ // because the zonecut processing actually takes place at the node.
+ // But the RRSIG-only for DNAME shouldn't confuse the process and the NS
+ // should win.
+ ConstRRsetPtr ns_rrset =
+ textToRRset("nodname.example.org. 300 IN NS ns.nodname.example.org.");
+ addToZoneData(ns_rrset);
+ findTest(Name("www.nodname.example.org"), RRType::A(),
+ ZoneFinder::DELEGATION, true, ns_rrset);
+}
+
+// \brief testcase for #2504 (Problem in inmem NSEC denial of existence
+// handling)
+TEST_F(InMemoryZoneFinderTest, NSECNonExistentTest) {
+ shared_ptr<ZoneTableSegment> ztable_segment(
+ new ZoneTableSegmentTest(class_, mem_sgmt_));
+ InMemoryClient client(ztable_segment, class_);
+ Name name("example.com.");
+
+ client.load(name, TEST_DATA_DIR "/2504-test.zone");
+ DataSourceClient::FindResult result(client.findZone(name));
+
+ // Check for a non-existing name
+ Name search_name("nonexist.example.com.");
+ ZoneFinderContextPtr find_result(
+ result.zone_finder->find(search_name,
+ RRType::A(), ZoneFinder::FIND_DNSSEC));
+ // We don't find the domain, but find() must complete (not throw or
+ // assert).
+ EXPECT_EQ(ZoneFinder::NXDOMAIN, find_result->code);
+}
+
/// \brief NSEC3 specific tests fixture for the InMemoryZoneFinder class
class InMemoryZoneFinderNSEC3Test : public InMemoryZoneFinderTest {
public:
@@ -1481,4 +1574,42 @@ TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3Walk) {
}
}
}
+
+TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
+ // add an RRSIG-only NSEC3 to the NSEC3 space, and try to find it; it
+ // should result in an exception.
+ const string n8_hash = "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ";
+ updater_.add(ConstRRsetPtr(),
+ textToRRset(
+ n8_hash + ".example.org. 300 IN RRSIG NSEC3 5 3 300 "
+ "20120814220826 20120715220826 1234 example.com. FAKE"));
+ EXPECT_THROW(zone_finder_.findNSEC3(Name("n8.example.org"), false),
+ DataSourceError);
+}
+
+// \brief testcase for #2503 (Problem in inmem NSEC3 denial of existence
+// handling)
+TEST_F(InMemoryZoneFinderNSEC3Test, findNSEC3MissingOrigin) {
+ // Set back the default hash calculator.
+ DefaultNSEC3HashCreator creator;
+ setNSEC3HashCreator(&creator);
+
+ shared_ptr<ZoneTableSegment> ztable_segment(
+ new ZoneTableSegmentTest(class_, mem_sgmt_));
+ InMemoryClient client(ztable_segment, class_);
+ Name name("example.com.");
+
+ client.load(name, TEST_DATA_DIR "/2503-test.zone");
+ DataSourceClient::FindResult result(client.findZone(name));
+
+ // Check for a non-existing name
+ Name search_name("nonexist.example.com.");
+ ZoneFinder::FindNSEC3Result find_result(
+ result.zone_finder->findNSEC3(search_name, true));
+ // findNSEC3() must have completed (not throw or assert). Because
+ // the find was recursive, it always must find something and return
+ // true.
+ EXPECT_TRUE(find_result.matched);
+}
+
}
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 5223d83..85be310 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -1219,7 +1219,7 @@ TEST_F(InMemoryZoneFinderTest, loadFromIterator) {
// purpose of this test, so it should just succeed.
db_client = unittest::createSQLite3Client(
class_, origin_, TEST_DATA_BUILDDIR "/contexttest.sqlite3.copied",
- TEST_DATA_DIR "/contexttest.zone");
+ TEST_DATA_DIR "/contexttest-almost-obsolete.zone");
zone_finder_.load(*db_client->getIterator(origin_));
// just checking a couple of RRs in the new version of zone.
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 100a0dd..c263304 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -20,6 +20,8 @@
#include <dns/rrclass.h>
+#include <exceptions/exceptions.h>
+
#include <sqlite3.h>
#include <gtest/gtest.h>
@@ -667,6 +669,64 @@ TEST_F(SQLite3Create, creationtest) {
ASSERT_TRUE(isReadable(SQLITE_NEW_DBFILE));
}
+// Test addZone works. This is done on the 'createtest' fixture so we
+// can easily be sure it does not exist yet.
+TEST_F(SQLite3Create, addZone) {
+ // Need shared_ptr for the getAllRecords at the end of the test
+ boost::shared_ptr<SQLite3Accessor> accessor(
+ new SQLite3Accessor(SQLITE_NEW_DBFILE, "IN"));
+
+ const std::string zone_name("example.com");
+ EXPECT_FALSE(accessor->getZone(zone_name).first);
+
+ // Calling addZone without transaction should fail
+ EXPECT_THROW(accessor->addZone(zone_name), DataSourceError);
+
+ // Add the zone. Returns the new zone id
+ accessor->startTransaction();
+ const int new_zone_id = accessor->addZone(zone_name);
+ accessor->commit();
+
+ // Check that it exists now, but has no records at this point
+ const std::pair<bool, int> zone_info(accessor->getZone(zone_name));
+ ASSERT_TRUE(zone_info.first);
+ EXPECT_EQ(new_zone_id, zone_info.second);
+
+ DatabaseAccessor::IteratorContextPtr context =
+ accessor->getAllRecords(zone_info.second);
+ string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_NE(DatabaseAccessor::IteratorContextPtr(), context);
+ EXPECT_FALSE(context->getNext(data));
+}
+
+// Test addZone does not get confused with different classes
+TEST_F(SQLite3Create, addZoneDifferentClass) {
+ const std::string zone_name("example.com.");
+
+ // Add IN zone
+ boost::shared_ptr<SQLite3Accessor> accessor(
+ new SQLite3Accessor(SQLITE_NEW_DBFILE, "IN"));
+ accessor->startTransaction();
+ const int new_zone_id_IN = accessor->addZone(zone_name);
+ accessor->commit();
+
+ // Add CH zone
+ accessor.reset(new SQLite3Accessor(SQLITE_NEW_DBFILE, "CH"));
+ accessor->startTransaction();
+ const int new_zone_id_CH = accessor->addZone(zone_name);
+ accessor->commit();
+
+ // id's should differ
+ ASSERT_NE(new_zone_id_IN, new_zone_id_CH);
+
+ // Reopen the database for both classes, and make sure
+ // we get the right zone id on searches
+ accessor.reset(new SQLite3Accessor(SQLITE_NEW_DBFILE, "IN"));
+ EXPECT_EQ(new_zone_id_IN, accessor->getZone(zone_name).second);
+ accessor.reset(new SQLite3Accessor(SQLITE_NEW_DBFILE, "CH"));
+ EXPECT_EQ(new_zone_id_CH, accessor->getZone(zone_name).second);
+}
+
TEST_F(SQLite3Create, emptytest) {
ASSERT_FALSE(isReadable(SQLITE_NEW_DBFILE));
@@ -1538,4 +1598,69 @@ TEST_F(SQLite3Update, addDiffWithUpdate) {
checkDiffs(expected_stored, accessor->getDiffs(zone_id, 1234, 1300));
}
+
+TEST_F(SQLite3Update, addZoneWhileLocked) {
+ const std::string existing_zone = "example.com.";
+ const std::string new_zone = "example2.com.";
+
+ // Start 'replacing' an existing zone, it should lock the db
+ zone_id = accessor->startUpdateZone(existing_zone, true).second;
+
+ // addZone should throw an exception that it is locked
+ another_accessor->startTransaction();
+ EXPECT_THROW(another_accessor->addZone(new_zone), DataSourceError);
+ // Commit should do nothing, but not fail
+ another_accessor->commit();
+
+ accessor->rollback();
+ // New zone should not exist
+ EXPECT_FALSE(accessor->getZone(new_zone).first);
+}
+
+//
+// Tests for deleteZone() follow.
+//
+TEST_F(SQLite3Update, deleteZone) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Calling deleteZone without transaction should fail
+ EXPECT_THROW(accessor->deleteZone(zone_info.first), isc::InvalidOperation);
+
+ // Delete the zone. Then confirm it, both before and after commit.
+ accessor->startTransaction();
+ accessor->deleteZone(zone_info.second);
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+ accessor->commit();
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+
+ // Records are not deleted.
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_TRUE(accessor->getRecords("example.com.", zone_id, false)
+ ->getNext(data));
+}
+
+TEST_F(SQLite3Update, deleteZoneWhileLocked) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Adding another (not commit yet), it should lock the db
+ const std::string new_zone = "new.example.com.";
+ accessor->startTransaction();
+ zone_id = accessor->addZone(new_zone);
+
+ // deleteZone should throw an exception that it is locked
+ another_accessor->startTransaction();
+ EXPECT_THROW(another_accessor->deleteZone(zone_id), DataSourceError);
+ // Commit should do nothing, but not fail
+ another_accessor->commit();
+
+ accessor->rollback();
+
+ // The zone should still exist.
+ EXPECT_TRUE(accessor->getZone("example.com.").first);
+}
+
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/testdata/checkwarn.zone b/src/lib/datasrc/tests/testdata/checkwarn.zone
new file mode 100644
index 0000000..e985085
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/checkwarn.zone
@@ -0,0 +1,4 @@
+example.org. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+example.org. 86400 IN NS ns.example.org.
+; Missing the address for the nameserver. This should generate a warning, but not error.
+www.example.org. 3600 IN A 192.0.2.1
diff --git a/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
new file mode 100644
index 0000000..f65f8c1
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/contexttest-almost-obsolete.zone
@@ -0,0 +1,85 @@
+;; test zone file used for ZoneFinderContext tests.
+;; RRSIGs are (obviouslly) faked ones for testing.
+
+example.org. 3600 IN SOA ns1.example.org. bugs.x.w.example.org. 78 3600 300 3600000 3600
+example.org. 3600 IN NS ns1.example.org.
+example.org. 3600 IN NS ns2.example.org.
+example.org. 3600 IN RRSIG NS 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+example.org. 3600 IN MX 1 mx1.example.org.
+example.org. 3600 IN MX 2 mx2.example.org.
+example.org. 3600 IN MX 3 mx.a.example.org.
+
+ns1.example.org. 3600 IN A 192.0.2.1
+ns1.example.org. 3600 IN RRSIG A 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+ns1.example.org. 3600 IN AAAA 2001:db8::1
+ns1.example.org. 3600 IN RRSIG AAAA 7 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKEFAKE
+ns2.example.org. 3600 IN A 192.0.2.2
+ns2.example.org. 3600 IN TXT "text data"
+
+mx1.example.org. 3600 IN A 192.0.2.10
+mx2.example.org. 3600 IN AAAA 2001:db8::10
+
+;; delegation
+a.example.org. 3600 IN NS ns1.a.example.org.
+a.example.org. 3600 IN NS ns2.a.example.org.
+a.example.org. 3600 IN NS ns.example.com.
+
+ns1.a.example.org. 3600 IN A 192.0.2.5
+ns2.a.example.org. 3600 IN A 192.0.2.6
+ns2.a.example.org. 3600 IN AAAA 2001:db8::6
+mx.a.example.org. 3600 IN A 192.0.2.7
+
+;; delegation, one of its NS names is at zone cut.
+b.example.org. 3600 IN NS ns.b.example.org.
+b.example.org. 3600 IN NS b.example.org.
+b.example.org. 3600 IN AAAA 2001:db8::8
+
+ns.b.example.org. 3600 IN A 192.0.2.9
+
+;; The MX name is at a zone cut. shouldn't be included in the
+;; additional section.
+mxatcut.example.org. 3600 IN MX 1 b.example.org.
+
+;; delegation, one of its NS names is under a DNAME delegation point;
+;; another is at that point; and yet another is under DNAME below a
+;; zone cut.
+c.example.org. 3600 IN NS ns.dname.example.org.
+c.example.org. 3600 IN NS dname.example.org.
+c.example.org. 3600 IN NS ns.deepdname.example.org.
+ns.dname.example.org. 3600 IN A 192.0.2.11
+dname.example.org. 3600 IN A 192.0.2.12
+ns.deepdname.example.org. 3600 IN AAAA 2001:db8::9
+
+;; delegation, one of its NS name is at an empty non terminal.
+d.example.org. 3600 IN NS ns.empty.example.org.
+d.example.org. 3600 IN NS ns1.example.org.
+;; by adding these two we can create an empty RB node for
+;; ns.empty.example.org in the in-memory zone
+foo.ns.empty.example.org. 3600 IN A 192.0.2.13
+bar.ns.empty.example.org. 3600 IN A 192.0.2.14
+
+;; delegation; the NS name matches a wildcard (and there's no exact
+;; match). One of the NS names matches an empty wildcard node, for
+;; which no additional record should be provided (or any other
+;; disruption should happen).
+e.example.org. 3600 IN NS ns.wild.example.org.
+e.example.org. 3600 IN NS ns.emptywild.example.org.
+e.example.org. 3600 IN NS ns2.example.org.
+*.wild.example.org. 3600 IN A 192.0.2.15
+a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
+
+;; additional for an answer RRset (MX) as a result of wildcard
+;; expansion
+*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
+
+;; the owner name of additional for an answer RRset (MX) has DNAME
+dnamemx.example.org. 3600 IN MX 1 dname.example.org.
+
+;; CNAME
+alias.example.org. 3600 IN CNAME cname.example.org.
+
+;; DNAME
+dname.example.org. 3600 IN DNAME dname.example.com.
+
+;; DNAME under a NS (strange one)
+deepdname.c.example.org. 3600 IN DNAME deepdname.example.com.
diff --git a/src/lib/datasrc/tests/testdata/contexttest.zone b/src/lib/datasrc/tests/testdata/contexttest.zone
index 0c1393c..ae028c4 100644
--- a/src/lib/datasrc/tests/testdata/contexttest.zone
+++ b/src/lib/datasrc/tests/testdata/contexttest.zone
@@ -68,6 +68,13 @@ e.example.org. 3600 IN NS ns2.example.org.
*.wild.example.org. 3600 IN A 192.0.2.15
a.*.emptywild.example.org. 3600 IN AAAA 2001:db8::2
+;; One of the additional records actually only has RRSIG, which should
+;; be ignored.
+f.example.org. 3600 IN MX 5 mx1.f.example.org.
+f.example.org. 3600 IN MX 10 mx2.f.example.org.
+mx1.f.example.org. 3600 IN RRSIG A 5 3 3600 20150420235959 20051021000000 40430 example.org. FAKEFAKE
+mx2.f.example.org. 3600 IN A 192.0.2.16
+
;; additional for an answer RRset (MX) as a result of wildcard
;; expansion
*.wildmx.example.org. 3600 IN MX 1 mx1.example.org.
diff --git a/src/lib/datasrc/tests/testdata/novalidate.zone b/src/lib/datasrc/tests/testdata/novalidate.zone
new file mode 100644
index 0000000..4bbf12a
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/novalidate.zone
@@ -0,0 +1,3 @@
+example.org. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+; Missing the NS here, will generate an error in post-load check of the zone.
+www.example.org. 3600 IN A 192.0.2.1
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 1a4cae2..85b167e 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -81,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
}
DataSourceClientPtr
-createSQLite3Client(RRClass zclass, const Name& zname) {
+createSQLite3Client(RRClass zclass, const Name& zname, stringstream& ss) {
// We always begin with an empty template SQLite3 DB file and install
// the zone data from the zone file to ensure both cases have the
// same test data.
@@ -93,7 +93,6 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
// Note that neither updater nor SQLite3 accessor checks this condition,
// so this should succeed.
ZoneUpdaterPtr updater = client->getUpdater(zname, false);
- stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
masterLoad(ss, Name::ROOT_NAME(), zclass,
boost::bind(addRRset, updater, _1));
updater->commit();
@@ -101,6 +100,12 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
return (client);
}
+DataSourceClientPtr
+createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
+ stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+ return (createSQLite3Client(zclass, zname, ss));
+}
+
// The test class. Its parameterized so we can share the test scnearios
// for any concrete data source implementaitons.
class ZoneFinderContextTest :
@@ -134,7 +139,7 @@ protected:
// We test the in-memory and SQLite3 data source implementations.
INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
::testing::Values(createInMemoryClient,
- createSQLite3Client));
+ createSQLite3ClientWithNS));
TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
@@ -418,4 +423,37 @@ TEST_P(ZoneFinderContextTest, getAdditionalForAny) {
result_sets_.begin(), result_sets_.end());
}
+TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
+ // This has two MX records, but type-A for one of them only has RRSIG.
+ // It shouldn't be contained in the result.
+ ZoneFinderContextPtr ctx = finder_->find(Name("f.example.org"),
+ RRType::MX(),
+ ZoneFinder::FIND_DNSSEC);
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ ctx->getAdditional(REQUESTED_BOTH, result_sets_);
+ rrsetsCheck("mx2.f.example.org. 3600 IN A 192.0.2.16\n",
+ result_sets_.begin(), result_sets_.end());
+}
+
+TEST(ZoneFinderContextSQLite3Test, escapedText) {
+ // This test checks that TXTLike data, when written to a database,
+ // is escaped correctly before stored in the database. The actual
+ // escaping is done in the toText() method of TXTLike objects, but
+ // we check anyway if this also carries over to the ZoneUpdater.
+ RRClass zclass(RRClass::IN());
+ Name zname("example.org");
+ stringstream ss("escaped.example.org. 3600 IN TXT Hello~World\\;\\\"");
+ DataSourceClientPtr client = createSQLite3Client(zclass, zname, ss);
+ ZoneFinderPtr finder = client->findZone(zname).zone_finder;
+
+ // If there is no escaping, the following will throw an exception
+ // when it tries to construct a TXT RRset using the data from the
+ // database.
+ ZoneFinderContextPtr ctx = finder->find(Name("escaped.example.org"),
+ RRType::TXT());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ EXPECT_EQ("escaped.example.org. 3600 IN TXT \"Hello~World\\;\\\"\"\n",
+ ctx->rrset->toText());
+}
+
}
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
new file mode 100644
index 0000000..28153e4
--- /dev/null
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -0,0 +1,544 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/zone_loader.h>
+#include <datasrc/data_source.h>
+
+#include <datasrc/memory/zone_table_segment.h>
+#include <datasrc/memory/memory_client.h>
+
+#include <dns/rrclass.h>
+#include <dns/name.h>
+#include <dns/rrset.h>
+#include <util/memory_segment_local.h>
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/foreach.hpp>
+#include <string>
+#include <vector>
+
+using isc::dns::RRClass;
+using isc::dns::Name;
+using isc::dns::RRType;
+using isc::dns::ConstRRsetPtr;
+using isc::dns::RRsetPtr;
+using std::string;
+using std::vector;
+using boost::shared_ptr;
+using namespace isc::datasrc;
+
+namespace {
+
+class MockClient : public DataSourceClient {
+public:
+ MockClient() :
+ commit_called_(false),
+ missing_zone_(false),
+ rrclass_(RRClass::IN())
+ {}
+ virtual FindResult findZone(const Name&) const {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ };
+ virtual std::pair<ZoneJournalReader::Result, ZoneJournalReaderPtr>
+ getJournalReader(const Name&, uint32_t, uint32_t) const
+ {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ virtual ZoneUpdaterPtr getUpdater(const Name& name, bool replace,
+ bool journaling) const;
+ // We store some information about what was happening here.
+ // It is publicly accessible, since this is private testing class
+ // anyway, so no need to dress it fancy into getters. Some are mutable,
+ // since many client methods are const, but we still want to know they
+ // were called.
+ mutable vector<Name> provided_updaters_;
+ vector<RRsetPtr> rrsets_;
+ // List of rrsets as texts, for easier manipulation
+ vector<string> rrset_texts_;
+ bool commit_called_;
+ // If set to true, getUpdater returns NULL
+ bool missing_zone_;
+ // The pretended class of the client. Usualy IN, but can be overriden.
+ RRClass rrclass_;
+};
+
+// Test implementation of RRsetCollectionBase.
+class TestRRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+ TestRRsetCollection(ZoneUpdater& updater,
+ const isc::dns::RRClass& rrclass) :
+ isc::datasrc::RRsetCollectionBase(updater, rrclass)
+ {}
+
+ virtual ~TestRRsetCollection() {}
+
+protected:
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+};
+
+// The updater isn't really correct according to the API. For example,
+// the whole client can be committed only once in its lifetime. The
+// updaters would influence each other if there were more. But we
+// don't need more updaters in the same test, so it doesn't matter
+// and this way, it is much simpler.
+class Updater : public ZoneUpdater {
+public:
+ Updater(MockClient* client, const Name& name) :
+ client_(client),
+ finder_(client_->rrclass_, name, client_->rrsets_)
+ {}
+ virtual ZoneFinder& getFinder() {
+ return (finder_);
+ }
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ if (!rrset_collection_) {
+ rrset_collection_.reset(new TestRRsetCollection(*this,
+ client_->rrclass_));
+ }
+ return (*rrset_collection_);
+ }
+ virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
+ if (client_->commit_called_) {
+ isc_throw(DataSourceError, "Add after commit");
+ }
+ // We need to copy the RRset. We don't do it properly (we omit the
+ // signature, for example), because we don't need to.
+ RRsetPtr new_rrset(new isc::dns::BasicRRset(rrset.getName(),
+ rrset.getClass(),
+ rrset.getType(),
+ rrset.getTTL()));
+ for (isc::dns::RdataIteratorPtr i(rrset.getRdataIterator());
+ !i->isLast(); i->next()) {
+ new_rrset->addRdata(i->getCurrent());
+ }
+ client_->rrsets_.push_back(new_rrset);
+ client_->rrset_texts_.push_back(rrset.toText());
+ }
+ virtual void deleteRRset(const isc::dns::AbstractRRset&) {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ virtual void commit() {
+ client_->commit_called_ = true;
+ }
+private:
+ MockClient* client_;
+ boost::scoped_ptr<TestRRsetCollection> rrset_collection_;
+ class Finder : public ZoneFinder {
+ public:
+ Finder(const RRClass& rrclass, const Name& name,
+ const vector<RRsetPtr>& rrsets) :
+ class_(rrclass),
+ name_(name),
+ rrsets_(rrsets)
+ {}
+ virtual RRClass getClass() const {
+ return (class_);
+ }
+ virtual Name getOrigin() const {
+ return (name_);
+ }
+ virtual shared_ptr<Context> find(const Name& name, const RRType& type,
+ const FindOptions options)
+ {
+ // The method is not completely correct. It ignores many special
+ // cases and also the options except for the result. But this is
+ // enough for the tests. We care only about exact match here.
+ BOOST_FOREACH(const RRsetPtr& rrset, rrsets_) {
+ if (rrset->getName() == name && rrset->getType() == type) {
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(SUCCESS, rrset))));
+ }
+ }
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(NXRRSET, ConstRRsetPtr()))));
+ }
+ virtual shared_ptr<Context> findAll(const Name&,
+ vector<ConstRRsetPtr>&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ virtual FindNSEC3Result findNSEC3(const Name&, bool) {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ private:
+ const RRClass class_;
+ const Name name_;
+ const vector<RRsetPtr>& rrsets_;
+ } finder_;
+};
+
+ZoneUpdaterPtr
+MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
+ if (missing_zone_) {
+ return (ZoneUpdaterPtr());
+ }
+ EXPECT_TRUE(replace);
+ EXPECT_FALSE(journaling);
+ provided_updaters_.push_back(name);
+ // const_cast is bad. But the const on getUpdater seems wrong in the first
+ // place, since updater will be modifying the data there. And the updater
+ // wants to store data into the client so we can examine it later.
+ return (ZoneUpdaterPtr(new Updater(const_cast<MockClient*>(this), name)));
+}
+
+class ZoneLoaderTest : public ::testing::Test {
+protected:
+ ZoneLoaderTest() :
+ rrclass_(RRClass::IN()),
+ ztable_segment_(memory::ZoneTableSegment::
+ create(isc::data::NullElement(), rrclass_)),
+ source_client_(ztable_segment_, rrclass_)
+ {}
+ void prepareSource(const Name& zone, const char* filename) {
+ // TODO:
+ // Currently, source_client_ is of InMemoryClient and its load()
+ // uses a different code than the ZoneLoader (so we can cross-check
+ // the implementations). Currently, the load() doesn't perform any
+ // post-load checks. It will change in #2499, at which point the
+ // loading may start failing depending on details of the test data. We
+ // should prepare the data by some different method then.
+ source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
+ }
+private:
+ const RRClass rrclass_;
+ // This is because of the in-memory client. We use it to read data
+ // from. It is still easier than setting up sqlite3 client, since
+ // we have this one in the linked library.
+
+ // FIXME: We should be destroying it by ZoneTableSegment::destroy.
+ // But the shared pointer won't let us, will it?
+ shared_ptr<memory::ZoneTableSegment> ztable_segment_;
+protected:
+ memory::InMemoryClient source_client_;
+ // This one is mocked. It will help us see what is happening inside.
+ // Also, mocking it is simpler than setting up an sqlite3 client.
+ MockClient destination_client_;
+};
+
+// Use the loader to load an unsigned zone.
+TEST_F(ZoneLoaderTest, copyUnsigned) {
+ prepareSource(Name::ROOT_NAME(), "root.zone");
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+ // It gets the updater directly in the constructor
+ ASSERT_EQ(1, destination_client_.provided_updaters_.size());
+ EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+
+ // Counter is initialized to 0, progress is "unknown" in case of copy.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
+ // Now load the whole zone
+ loader.load();
+ EXPECT_TRUE(destination_client_.commit_called_);
+ // We don't check the whole zone. We check the first and last and the
+ // count, which should be enough.
+
+ // The count is 34 because we expect the RRs to be separated.
+ EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // Check various counters. getRRCount should be identical of the RRs
+ // we've seen. Progress is still "unknown" in the copy operation.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
+ // Ensure known order.
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
+ EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+ destination_client_.rrset_texts_.front());
+ EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+ destination_client_.rrset_texts_.back());
+
+ // It isn't possible to try again now
+ EXPECT_THROW(loader.load(), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+ // Even 0, which should load nothing, returns the error
+ EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Try loading incrementally.
+TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
+ prepareSource(Name::ROOT_NAME(), "root.zone");
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(), source_client_);
+
+ // Try loading few RRs first.
+ loader.loadIncremental(10);
+ // We should get the 10 we asked for
+ EXPECT_EQ(10, destination_client_.rrsets_.size());
+ // Not committed yet, we didn't complete the loading
+ EXPECT_FALSE(destination_client_.commit_called_);
+
+ // Check we can get intermediate counters. Progress is always "unknown"
+ // in case of copy.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
+ // This is unusual, but allowed. Check it doesn't do anything
+ loader.loadIncremental(0);
+ EXPECT_EQ(10, destination_client_.rrsets_.size());
+ EXPECT_FALSE(destination_client_.commit_called_);
+
+ // We can finish the rest
+ loader.loadIncremental(30);
+ EXPECT_EQ(34, destination_client_.rrsets_.size());
+ EXPECT_TRUE(destination_client_.commit_called_);
+
+ // No more loading now
+ EXPECT_THROW(loader.load(), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Check we can load RRSIGs and NSEC3 (which could break due to them being
+// in separate namespace)
+TEST_F(ZoneLoaderTest, copySigned) {
+ prepareSource(Name("example.org"), "example.org.nsec3-signed");
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ source_client_);
+ loader.load();
+
+ // All the RRs are there, including the ones in NSEC3 namespace
+ EXPECT_EQ(14, destination_client_.rrsets_.size());
+ EXPECT_TRUE(destination_client_.commit_called_);
+ // Same trick with sorting to know where they are
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
+ // Due to the R at the beginning, this one should be last
+ EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
+ "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
+ destination_client_.rrset_texts_[0]);
+ EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
+ "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
+ " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
+ "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
+ "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
+ destination_client_.rrset_texts_[1]);
+}
+
+// If the destination zone does not exist, it throws
+TEST_F(ZoneLoaderTest, copyMissingDestination) {
+ destination_client_.missing_zone_ = true;
+ prepareSource(Name::ROOT_NAME(), "root.zone");
+ EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+ source_client_), DataSourceError);
+}
+
+// If the source zone does not exist, it throws
+TEST_F(ZoneLoaderTest, copyMissingSource) {
+ EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+ source_client_), DataSourceError);
+}
+
+// The class of the source and destination are different
+TEST_F(ZoneLoaderTest, classMismatch) {
+ destination_client_.rrclass_ = RRClass::CH();
+ prepareSource(Name::ROOT_NAME(), "root.zone");
+ EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+ source_client_), isc::InvalidParameter);
+}
+
+// Load an unsigned zone, all at once
+TEST_F(ZoneLoaderTest, loadUnsigned) {
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+ TEST_DATA_DIR "/root.zone");
+
+ // Counter and progress are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
+ // It gets the updater directly in the constructor
+ ASSERT_EQ(1, destination_client_.provided_updaters_.size());
+ EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+ // Now load the whole zone
+ loader.load();
+ EXPECT_TRUE(destination_client_.commit_called_);
+ // We don't check the whole zone. We check the first and last and the
+ // count, which should be enough.
+
+ // The count is 34 because we expect the RRs to be separated.
+ EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // getRRCount should be identical of the RRs we've seen. progress
+ // should reach 100% (= 1).
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
+ // Ensure known order.
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
+ EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+ destination_client_.rrset_texts_.front());
+ EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+ destination_client_.rrset_texts_.back());
+
+ // It isn't possible to try again now
+ EXPECT_THROW(loader.load(), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+ // Even 0, which should load nothing, returns the error
+ EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// Try loading from master file incrementally.
+TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+ TEST_DATA_DIR "/root.zone");
+
+ // Counters are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
+ // Try loading few RRs first.
+ loader.loadIncremental(10);
+ // We should get the 10 we asked for
+ EXPECT_EQ(10, destination_client_.rrsets_.size());
+ // Not committed yet, we didn't complete the loading
+ EXPECT_FALSE(destination_client_.commit_called_);
+
+ EXPECT_EQ(10, destination_client_.rrsets_.size());
+ EXPECT_FALSE(destination_client_.commit_called_);
+
+ // Check we can get intermediate counters. Expected progress is calculated
+ // based on the size of the zone file and the offset to the end of 10th RR
+ // (subject to future changes to the file, but we assume it's a rare
+ // event.). The expected value should be the exact expression that
+ // getProgress() should do internally, so EXPECT_EQ() should work here,
+ // but floating-point comparison can be always tricky we use
+ // EXPECT_DOUBLE_EQ just in case.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ // file size = 1541, offset = 428 (27.77%).
+ EXPECT_DOUBLE_EQ(static_cast<double>(428) / 1541, loader.getProgress());
+
+ // We can finish the rest
+ loader.loadIncremental(30);
+ EXPECT_EQ(34, destination_client_.rrsets_.size());
+ EXPECT_TRUE(destination_client_.commit_called_);
+
+ // Counters are updated accordingly. Progress should reach 100%.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
+ // No more loading now
+ EXPECT_THROW(loader.load(), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
+ EXPECT_THROW(loader.loadIncremental(0), isc::InvalidOperation);
+}
+
+// If the destination zone does not exist, it throws
+TEST_F(ZoneLoaderTest, loadMissingDestination) {
+ destination_client_.missing_zone_ = true;
+ EXPECT_THROW(ZoneLoader(destination_client_, Name::ROOT_NAME(),
+ TEST_DATA_DIR "/root.zone"), DataSourceError);
+}
+
+// Check we can load RRSIGs and NSEC3 (which could break due to them being
+// in separate namespace)
+TEST_F(ZoneLoaderTest, loadSigned) {
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ TEST_DATA_DIR "/example.org.nsec3-signed");
+ loader.load();
+
+ // All the RRs are there, including the ones in NSEC3 namespace
+ EXPECT_EQ(14, destination_client_.rrsets_.size());
+ EXPECT_TRUE(destination_client_.commit_called_);
+ // Same trick with sorting to know where they are
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
+ // Due to the R at the beginning, this one should be last
+ EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
+ "1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
+ destination_client_.rrset_texts_[0]);
+ EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
+ "NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
+ " EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
+ "KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
+ "/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
+ destination_client_.rrset_texts_[1]);
+}
+
+// Test it throws when there's no such file
+TEST_F(ZoneLoaderTest, loadNoSuchFile) {
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+ "This file does not exist");
+ EXPECT_THROW(loader.load(), MasterFileError);
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// And it also throws when there's a syntax error in the master file
+TEST_F(ZoneLoaderTest, loadSyntaxError) {
+ ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
+ // This is not a master file for sure
+ // (misusing a file that happens to be there
+ // already).
+ TEST_DATA_DIR "/example.org.sqlite3");
+ EXPECT_THROW(loader.load(), MasterFileError);
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// Test there's validation of the data in the zone loader.
+TEST_F(ZoneLoaderTest, loadCheck) {
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ TEST_DATA_DIR "/novalidate.zone");
+ EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+ // The messages go to the log. We don't have an easy way to examine them.
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// The same test, but for copying from other data source
+TEST_F(ZoneLoaderTest, copyCheck) {
+ prepareSource(Name("example.org"), "novalidate.zone");
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ source_client_);
+
+ EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+ // The messages go to the log. We don't have an easy way to examine them.
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// Check a warning doesn't disrupt the loading of the zone
+TEST_F(ZoneLoaderTest, loadCheckWarn) {
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ TEST_DATA_DIR "/checkwarn.zone");
+ EXPECT_TRUE(loader.loadIncremental(10));
+ // The messages go to the log. We don't have an easy way to examine them.
+ // But the zone was committed and contains all 3 RRs
+ EXPECT_TRUE(destination_client_.commit_called_);
+ EXPECT_EQ(3, destination_client_.rrsets_.size());
+}
+
+TEST_F(ZoneLoaderTest, copyCheckWarn) {
+ prepareSource(Name("example.org"), "checkwarn.zone");
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ source_client_);
+ EXPECT_TRUE(loader.loadIncremental(10));
+ // The messages go to the log. We don't have an easy way to examine them.
+ // But the zone was committed and contains all 3 RRs
+ EXPECT_TRUE(destination_client_.commit_called_);
+ EXPECT_EQ(3, destination_client_.rrsets_.size());
+
+}
+
+}
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 0d7438d..01d6a83 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -21,6 +21,7 @@
#include <datasrc/exceptions.h>
#include <datasrc/result.h>
+#include <datasrc/rrset_collection_base.h>
#include <utility>
#include <vector>
@@ -740,6 +741,9 @@ typedef boost::shared_ptr<ZoneFinder::Context> ZoneFinderContextPtr;
/// \c ZoneFinder::Context object.
typedef boost::shared_ptr<ZoneFinder::Context> ConstZoneFinderContextPtr;
+/// \brief A forward declaration
+class RRsetCollectionBase;
+
/// The base class to make updates to a single zone.
///
/// On construction, each derived class object will start a "transaction"
@@ -802,6 +806,29 @@ public:
/// \return A reference to a \c ZoneFinder for the updated zone
virtual ZoneFinder& getFinder() = 0;
+ /// Return an RRsetCollection for the updater.
+ ///
+ /// This method returns an \c RRsetCollection for the updater,
+ /// implementing the \c isc::datasrc::RRsetCollectionBase
+ /// interface. Typically, the returned \c RRsetCollection is a
+ /// singleton for its \c ZoneUpdater. The returned RRsetCollection
+ /// object must not be used after its corresponding \c ZoneUpdater
+ /// has been destroyed. The returned RRsetCollection object may be
+ /// used to search RRsets from the ZoneUpdater. The actual
+ /// \c RRsetCollection returned has a behavior dependent on the
+ /// \c ZoneUpdater implementation.
+ ///
+ /// The behavior of the RRsetCollection is similar to the behavior
+ /// of the \c Zonefinder returned by \c getFinder().
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called.
+ /// Implementations of \c ZoneUpdater may disable a previously
+ /// returned \c RRsetCollection after \c commit() is called. If an
+ /// \c RRsetCollection is disabled, using methods such as \c find()
+ /// and using its iterator would cause an exception to be
+ /// thrown. See \c isc::datasrc::RRsetCollectionBase for details.
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() = 0;
+
/// Add an RRset to a zone via the updater
///
/// This may be revisited in a future version, but right now the intended
@@ -849,6 +876,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called. In this
+ /// case, implementations throw an \c InvalidOperation exception.
+ ///
/// If journaling was requested when getting this updater, it will reject
/// to add the RRset if the squence doesn't look like and IXFR (see
/// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
@@ -920,6 +951,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called. In this
+ /// case, implementations throw an \c InvalidOperation exception.
+ ///
/// If journaling was requested when getting this updater, it will reject
/// to add the RRset if the squence doesn't look like and IXFR (see
/// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
new file mode 100644
index 0000000..9e9dd4a
--- /dev/null
+++ b/src/lib/datasrc/zone_loader.cc
@@ -0,0 +1,221 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/zone_loader.h>
+#include <datasrc/master_loader_callbacks.h>
+
+#include <datasrc/client.h>
+#include <datasrc/data_source.h>
+#include <datasrc/iterator.h>
+#include <datasrc/zone.h>
+#include <datasrc/logger.h>
+#include <datasrc/rrset_collection_base.h>
+
+#include <dns/rrset.h>
+#include <dns/zone_checker.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
+
+using isc::dns::Name;
+using isc::dns::ConstRRsetPtr;
+using isc::dns::MasterLoader;
+using isc::dns::MasterLexer;
+
+namespace isc {
+namespace datasrc {
+
+const double ZoneLoader::PROGRESS_UNKNOWN = -1;
+
+ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
+ DataSourceClient& source) :
+ // Separate the RRsets as that is possibly faster (the data source doesn't
+ // have to aggregate them) and also because our limit semantics.
+ iterator_(source.getIterator(zone_name, true)),
+ updater_(destination.getUpdater(zone_name, true, false)),
+ complete_(false), rr_count_(0)
+{
+ // The getIterator should never return NULL. So we check it.
+ // Or should we throw instead?
+ assert(iterator_ != ZoneIteratorPtr());
+ // In case the zone doesn't exist in the destination, throw
+ if (updater_ == ZoneUpdaterPtr()) {
+ isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
+ "destination data source, can't fill it with data");
+ }
+ // The dereference of zone_finder is safe, if we can get iterator, we can
+ // get a finder.
+ //
+ // TODO: We probably need a getClass on the data source itself.
+ if (source.findZone(zone_name).zone_finder->getClass() !=
+ updater_->getFinder().getClass()) {
+ isc_throw(isc::InvalidParameter,
+ "Source and destination class mismatch");
+ }
+}
+
+namespace {
+// Unified callback to install RR and increment RR count at the same time.
+void
+addRR(ZoneUpdater* updater, size_t* rr_count,
+ const dns::Name& name, const dns::RRClass& rrclass,
+ const dns::RRType& type, const dns::RRTTL& ttl,
+ const dns::rdata::RdataPtr& data)
+{
+ isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+ rrset.addRdata(data);
+ updater->addRRset(rrset);
+ ++*rr_count;
+}
+}
+
+ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
+ const char* filename) :
+ updater_(destination.getUpdater(zone_name, true, false)),
+ complete_(false), loaded_ok_(true), rr_count_(0)
+{
+ if (updater_ == ZoneUpdaterPtr()) {
+ isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
+ "destination data source, can't fill it with data");
+ } else {
+ loader_.reset(new
+ MasterLoader(filename, zone_name,
+ // TODO: Maybe we should have getClass()
+ // on the data source?
+ updater_->getFinder().getClass(),
+ createMasterLoaderCallbacks(zone_name,
+ updater_->getFinder().getClass(),
+ &loaded_ok_),
+ boost::bind(addRR,
+ updater_.get(), &rr_count_,
+ _1, _2, _3, _4, _5)));
+ }
+}
+
+namespace {
+
+// Copy up to limit RRsets from source to destination
+bool
+copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
+ size_t limit, size_t& rr_count_)
+{
+ size_t loaded = 0;
+ while (loaded < limit) {
+ const ConstRRsetPtr rrset(source->getNextRRset());
+ if (rrset == ConstRRsetPtr()) {
+ // Done loading, no more RRsets in the input.
+ return (true);
+ } else {
+ destination->addRRset(*rrset);
+ }
+ ++loaded;
+ rr_count_ += rrset->getRdataCount();
+ }
+ return (false); // Not yet, there may be more
+}
+
+void
+logWarning(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_WARN(logger, DATASRC_CHECK_WARNING).arg(*zone_name).arg(*rrclass).
+ arg(reason);
+}
+
+void
+logError(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_ERROR(logger, DATASRC_CHECK_ERROR).arg(*zone_name).arg(*rrclass).
+ arg(reason);
+}
+
+} // end unnamed namespace
+
+bool
+ZoneLoader::loadIncremental(size_t limit) {
+ if (complete_) {
+ isc_throw(isc::InvalidOperation,
+ "Loading has been completed previously");
+ }
+
+ if (!iterator_) {
+ assert(loader_);
+ try {
+ complete_ = loader_->loadIncremental(limit);
+ } catch (const isc::dns::MasterLoaderError& e) {
+ isc_throw(MasterFileError, e.getMessage().c_str());
+ }
+ if (complete_ && !loaded_ok_) {
+ isc_throw(MasterFileError, "Error while loading master file");
+ }
+ } else {
+ complete_ = copyRRsets(updater_, iterator_, limit, rr_count_);
+ }
+
+ if (complete_) {
+ // Everything is loaded. Perform some basic sanity checks on the zone.
+ RRsetCollectionBase& collection = updater_->getRRsetCollection();
+ const dns::Name& zone_name(updater_->getFinder().getOrigin());
+ const dns::RRClass& zone_class(updater_->getFinder().getClass());
+ const dns::ZoneCheckerCallbacks
+ callbacks(boost::bind(&logError, &zone_name, &zone_class, _1),
+ boost::bind(&logWarning, &zone_name, &zone_class, _1));
+ if (!dns::checkZone(zone_name, zone_class, collection, callbacks)) {
+ // The post-load check failed.
+ loaded_ok_ = false;
+ isc_throw(ZoneContentError, "Errors found when validating zone " <<
+ zone_name << "/" << zone_class);
+ }
+ updater_->commit();
+ }
+ return (complete_);
+}
+
+size_t
+ZoneLoader::getRRCount() const {
+ return (rr_count_);
+}
+
+double
+ZoneLoader::getProgress() const {
+ if (!loader_) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ const size_t pos = loader_->getPosition();
+ const size_t total_size = loader_->getSize();
+
+ // If the current position is 0, progress should definitely be 0; we
+ // don't bother to check the total size even if it's "unknown".
+ if (pos == 0) {
+ return (0);
+ }
+
+ // These cases shouldn't happen with our usage of MasterLoader. So, in
+ // theory, we could throw here; however, since this method is expected
+ // to be used for informational purposes only, that's probably too harsh.
+ // So we return "unknown" instead.
+ if (total_size == MasterLexer::SOURCE_SIZE_UNKNOWN || total_size == 0) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ return (static_cast<double>(pos) / total_size);
+}
+
+} // end namespace datasrc
+} // end namespace isc
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
new file mode 100644
index 0000000..2a4559e
--- /dev/null
+++ b/src/lib/datasrc/zone_loader.h
@@ -0,0 +1,238 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DATASRC_ZONE_LOADER_H
+#define DATASRC_ZONE_LOADER_H
+
+#include <datasrc/data_source.h>
+
+#include <dns/master_loader.h>
+
+#include <cstdlib> // For size_t
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace isc {
+namespace dns {
+// Forward declaration
+class Name;
+}
+namespace datasrc {
+
+// Forward declarations
+class DataSourceClient;
+class ZoneIterator;
+typedef boost::shared_ptr<ZoneIterator> ZoneIteratorPtr;
+class ZoneUpdater;
+typedef boost::shared_ptr<ZoneUpdater> ZoneUpdaterPtr;
+
+/// \brief Exception thrown when there's a problem with master file.
+///
+/// This is thrown by the ZoneLoader when there's a fatal problem with
+/// a master file being loaded.
+class MasterFileError : public DataSourceError {
+public:
+ MasterFileError(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what)
+ {}
+};
+
+/// \brief Exception thrown when the zone doesn't pass post-load check.
+///
+/// This is thrown by the ZoneLoader when the zone is loaded, but it
+/// doesn't pass basic sanity checks.
+class ZoneContentError : public DataSourceError {
+public:
+ ZoneContentError(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what)
+ {}
+};
+
+/// \brief Class to load data into a data source client.
+///
+/// This is a small wrapper class that is able to load data into a data source.
+/// It can load either from another data source or from a master file. The
+/// purpose of the class is only to hold the state for incremental loading.
+///
+/// The old content of zone is discarded and no journal is stored.
+class ZoneLoader {
+public:
+ /// \brief Constructor from master file.
+ ///
+ /// This initializes the zone loader to load from a master file.
+ ///
+ /// \param destination The data source into which the loaded data should
+ /// go.
+ /// \param zone_name The origin of the zone. The class is implicit in the
+ /// destination.
+ /// \param master_file Path to the master file to read data from.
+ /// \throw DataSourceError in case the zone does not exist in destination.
+ /// This class does not support creating brand new zones, only loading
+ /// data into them. In case a new zone is needed, it must be created
+ /// beforehand.
+ /// \throw DataSourceError in case of other (possibly low-level) errors,
+ /// such as read-only data source or database error.
+ ZoneLoader(DataSourceClient& destination, const isc::dns::Name& zone_name,
+ const char* master_file);
+
+ /// \brief Constructor from another data source.
+ ///
+ /// This initializes the zone loader to read from another data source.
+ /// It'll effectively copy data from one data source to another.
+ ///
+ /// \param destination The data source into which the loaded data should
+ /// go.
+ /// \param zone_name The origin of the zone.
+ /// \param source The data source from which the data would be read.
+ /// \throw InvalidParameter in case the class of destination and source
+ /// differs.
+ /// \throw NotImplemented in case the source data source client doesn't
+ /// provide an iterator.
+ /// \throw DataSourceError in case the zone does not exist in destination.
+ /// This class does not support creating brand new zones, only loading
+ /// data into them. In case a new zone is needed, it must be created
+ /// beforehand.
+ /// \throw DataSourceError in case the zone does not exist in the source.
+ /// \throw DataSourceError in case of other (possibly low-level) errors,
+ /// such as read-only data source or database error.
+ ZoneLoader(DataSourceClient& destination, const isc::dns::Name& zone_name,
+ DataSourceClient& source);
+
+ /// \brief Perform the whole load.
+ ///
+ /// This performs the whole loading operation. It may take a long time.
+ ///
+ /// \throw InvalidOperation in case the loading was already completed
+ /// before this call.
+ /// \throw DataSourceError in case some error (possibly low-level) happens.
+ /// \throw MasterFileError when the master_file is badly formatted or some
+ /// similar problem is found when loading the master file.
+ /// \throw ZoneContentError when the zone doesn't pass sanity check.
+ void load() {
+ while (!loadIncremental(1000)) { // 1000 is arbitrary largish number
+ // Body intentionally left blank.
+ }
+ }
+
+ /// \brief Load up to limit RRs.
+ ///
+ /// This performs a part of the loading. In case there's enough data in the
+ /// source, it copies limit RRs. It can copy less RRs during the final call
+ /// (when there's less than limit left).
+ ///
+ /// This can be called repeatedly until the whole zone is loaded, having
+ /// pauses in the loading for some purposes (for example reporting
+ /// progress).
+ ///
+ /// After the last RR is loaded, a sanity check of the zone is performed by
+ /// isc::dns::validateZone. It reports errors and warnings by logging them
+ /// directly. If there are any errors, a ZoneContentError exception is
+ /// thrown and the load is aborted (preserving the old version of zone, if
+ /// any).
+ ///
+ /// \param limit The maximum allowed number of RRs to be loaded during this
+ /// call.
+ /// \return True in case the loading is completed, false if there's more
+ /// to load.
+ /// \throw InvalidOperation in case the loading was already completed
+ /// before this call (by load() or by a loadIncremental that returned
+ /// true).
+ /// \throw DataSourceError in case some error (possibly low-level) happens.
+ /// \throw MasterFileError when the master_file is badly formatted or some
+ /// similar problem is found when loading the master file.
+ /// \throw ZoneContentError when the zone doesn't pass sanity check.
+ /// \note If the limit is exactly the number of RRs available to be loaded,
+ /// the method still returns false and true'll be returned on the next
+ /// call (which will load 0 RRs). This is because the end of iterator
+ /// or master file is detected when reading past the end, not when the
+ /// last one is read.
+ bool loadIncremental(size_t limit);
+
+ /// \brief Return the number of RRs loaded.
+ ///
+ /// This method returns the number of RRs loaded via this loader by the
+ /// time of the call. Before starting the load it will return 0.
+ /// It will return the total number of RRs of the zone on and after
+ /// completing the load.
+ ///
+ /// \throw None
+ size_t getRRCount() const;
+
+ /// \brief Return the current progress of the loader.
+ ///
+ /// This method returns the current estimated progress of loader as a
+ /// value between 0 and 1 (inclusive); it's 0 before starting the load,
+ /// and 1 at the completion, and a value between these (exclusive) in the
+ /// middle of loading. It's an implementation detail how to calculate
+ /// the progress, which may vary depending on how the loader is
+ /// constructed and may even be impossible to detect effectively.
+ ///
+ /// If the progress cannot be determined, this method returns a special
+ /// value of PROGRESS_UNKNOWN, which is not included in the range between
+ /// 0 and 1.
+ ///
+ /// As such, the application should use the return value only for
+ /// informational purposes such as logging. For example, it shouldn't
+ /// be used to determine whether loading is completed by comparing it
+ /// to 1. It should also expect the possibility of getting
+ /// \c PROGRESS_UNKNOWN at any call to this method; it shouldn't assume
+ /// the specific way of internal implementation as described below (which
+ /// is provided for informational purposes only).
+ ///
+ /// In this implementation, if the loader is constructed with a file
+ /// name, the progress value is measured by the number of characters
+ /// read from the zone file divided by the size of the zone file
+ /// (with taking into account any included files). Note that due to
+ /// the possibility of intermediate included files, the total file size
+ /// cannot be fully fixed until the completion of the load. And, due to
+ /// this possibility, return values from this method may not always
+ /// increase monotonically.
+ ///
+ /// If it's constructed with another data source client, this method
+ /// always returns \c PROGRESS_UNKNOWN; in future, however, it may become
+ /// possible to return something more useful, e.g, based on the result
+ /// of \c getRRCount() and the total number of RRs if the underlying data
+ /// source can provide the latter value efficiently.
+ ///
+ /// \throw None
+ double getProgress() const;
+
+ /// \brief A special value for \c getProgress, meaning the progress is
+ /// unknown.
+ ///
+ /// See the method description for details.
+ static const double PROGRESS_UNKNOWN;
+
+private:
+ /// \brief The iterator used as source of data in case of the copy mode.
+ const ZoneIteratorPtr iterator_;
+ /// \brief The destination zone updater
+ const ZoneUpdaterPtr updater_;
+ /// \brief The master loader (for the master file mode)
+ boost::scoped_ptr<isc::dns::MasterLoader> loader_;
+ /// \brief Indicator if loading was completed
+ bool complete_;
+ /// \brief Was the loading successful?
+ bool loaded_ok_;
+ size_t rr_count_;
+};
+
+}
+}
+
+#endif // DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 1c74446..822c4e3 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,32 +14,36 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libb10-dhcp++.la
libb10_dhcp___la_SOURCES =
+libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
libb10_dhcp___la_SOURCES += duid.cc duid.h
+libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
-libb10_dhcp___la_SOURCES += option.cc option.h
-libb10_dhcp___la_SOURCES += option_data_types.h
-libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
+libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
-libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
-libb10_dhcp___la_SOURCES += option6_int.h
-libb10_dhcp___la_SOURCES += option6_int_array.h
-libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
+libb10_dhcp___la_SOURCES += option_int.h
+libb10_dhcp___la_SOURCES += option_int_array.h
+libb10_dhcp___la_SOURCES += option.cc option.h
+libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
+libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
+libb10_dhcp___la_SOURCES += std_option_defs.h
libb10_dhcp___la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcp___la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcp___la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
+libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/dns/libb10-dns++.la
libb10_dhcp___la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
libb10_dhcp___la_LDFLAGS = -no-undefined -version-info 2:0:0
-EXTRA_DIST = README
+EXTRA_DIST = README libdhcp++.dox
if USE_CLANGPP
# Disable unused parameter warning caused by some of the
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index c3f46da..f1c8866 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -33,7 +33,7 @@ DUID::DUID(const std::vector<uint8_t>& duid) {
}
}
-DUID::DUID(const uint8_t * data, size_t len) {
+DUID::DUID(const uint8_t* data, size_t len) {
if (len > MAX_DUID_LEN) {
isc_throw(OutOfRange, "DUID too large");
}
@@ -72,36 +72,46 @@ std::string DUID::toText() const {
return (tmp.str());
}
-bool DUID::operator == (const DUID& other) const {
+bool DUID::operator==(const DUID& other) const {
return (this->duid_ == other.duid_);
}
-bool DUID::operator != (const DUID& other) const {
+bool DUID::operator!=(const DUID& other) const {
return (this->duid_ != other.duid_);
}
-/// constructor based on vector<uint8_t>
+// Constructor based on vector<uint8_t>
ClientId::ClientId(const std::vector<uint8_t>& clientid)
- :DUID(clientid) {
+ : DUID(clientid) {
}
-/// constructor based on C-style data
+// Constructor based on C-style data
ClientId::ClientId(const uint8_t *clientid, size_t len)
- :DUID(clientid, len) {
+ : DUID(clientid, len) {
}
-/// @brief returns a copy of client-id data
+// Returns a copy of client-id data
const std::vector<uint8_t> ClientId::getClientId() const {
return (duid_);
}
-// compares two client-ids
-bool ClientId::operator == (const ClientId& other) const {
+// Returns the Client ID in text form
+std::string ClientId::toText() const {
+
+ // As DUID is a private base class of ClientId, we can't access
+ // its public toText() method through inheritance: instead we
+ // need the interface of a ClientId::toText() that calls the
+ // equivalent method in the base class.
+ return (DUID::toText());
+}
+
+// Compares two client-ids
+bool ClientId::operator==(const ClientId& other) const {
return (this->duid_ == other.duid_);
}
-// compares two client-ids
-bool ClientId::operator != (const ClientId& other) const {
+// Compares two client-ids
+bool ClientId::operator!=(const ClientId& other) const {
return (this->duid_ != other.duid_);
}
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 001e362..688885b 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -45,13 +45,13 @@ class DUID {
DUID_MAX ///< not a real type, just maximum defined value + 1
} DUIDType;
- /// @brief creates a DUID
+ /// @brief Constructor from vector
DUID(const std::vector<uint8_t>& duid);
- /// @brief creates a DUID
- DUID(const uint8_t *duid, size_t len);
+ /// @brief Constructor from array and array size
+ DUID(const uint8_t* duid, size_t len);
- /// @brief returns a const reference to the actual DUID value
+ /// @brief Returns a const reference to the actual DUID value
///
/// Note: For safety reasons, this method returns a copy of data as
/// otherwise the reference would be only valid as long as the object that
@@ -60,49 +60,66 @@ class DUID {
/// (e.g. storeSelf()) that will avoid data copying.
const std::vector<uint8_t> getDuid() const;
- /// @brief returns DUID type
+ /// @brief Returns the DUID type
DUIDType getType() const;
- /// returns textual prepresentation (e.g. 00:01:02:03:ff)
+ /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
std::string toText() const;
- /// compares two DUIDs
+ /// @brief Compares two DUIDs for equality
bool operator==(const DUID& other) const;
- /// compares two DUIDs
+ /// @brief Compares two DUIDs for inequality
bool operator!=(const DUID& other) const;
protected:
- /// the actual content of the DUID
+ /// The actual content of the DUID
std::vector<uint8_t> duid_;
};
+/// @brief Shared pointer to a DUID
typedef boost::shared_ptr<DUID> DuidPtr;
+
+
/// @brief Holds Client identifier or client IPv4 address
///
/// This class is intended to be a generic IPv4 client identifier. It can hold
/// a client-id
-class ClientId : DUID {
- public:
+class ClientId : public DUID {
+public:
+ /// @brief Maximum size of a client ID
+ ///
+ /// This is the same as the maximum size of the underlying DUID.
+ ///
+ /// @note RFC 2131 does not specify an upper length of a client ID, the
+ /// value chosen here just being that of the underlying DUID. For
+ /// some backend database, there may be a possible (minor)
+ /// performance enhancement if this were smaller.
+ static const size_t MAX_CLIENT_ID_LEN = DUID::MAX_DUID_LEN;
- /// constructor based on vector<uint8_t>
+ /// @brief Constructor based on vector<uint8_t>
ClientId(const std::vector<uint8_t>& clientid);
- /// constructor based on C-style data
- ClientId(const uint8_t *clientid, size_t len);
+ /// @brief Constructor based on array and array size
+ ClientId(const uint8_t* clientid, size_t len);
- /// @brief returns reference to the client-id data
- ///
+ /// @brief Returns reference to the client-id data
const std::vector<uint8_t> getClientId() const;
- // compares two client-ids
- bool operator == (const ClientId& other) const;
+ /// @brief Returns textual representation of a DUID (e.g. 00:01:02:03:ff)
+ std::string toText() const;
+
+ /// @brief Compares two client-ids for equality
+ bool operator==(const ClientId& other) const;
- // compares two client-ids
- bool operator != (const ClientId& other) const;
+ /// @brief Compares two client-ids for inequality
+ bool operator!=(const ClientId& other) const;
};
+/// @brief Shared pointer to a Client ID.
+typedef boost::shared_ptr<ClientId> ClientIdPtr;
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
new file mode 100644
index 0000000..d19f2ad
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+HWAddr::HWAddr()
+ :htype_(HTYPE_ETHER) {
+}
+
+HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
+ :hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+}
+
+HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
+ :hwaddr_(hwaddr), htype_(htype) {
+}
+
+std::string HWAddr::toText() const {
+ std::stringstream tmp;
+ tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
+ it != hwaddr_.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+bool HWAddr::operator==(const HWAddr& other) const {
+ return ((this->htype_ == other.htype_) &&
+ (this->hwaddr_ == other.hwaddr_));
+}
+
+bool HWAddr::operator!=(const HWAddr& other) const {
+ return !(*this == other);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
new file mode 100644
index 0000000..93b06a1
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <vector>
+#include <stdint.h>
+#include <stddef.h>
+#include <dhcp/dhcp4.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware type that represents information from DHCPv4 packet
+struct HWAddr {
+public:
+
+ /// @brief default constructor
+ HWAddr();
+
+ /// @brief constructor, based on C-style pointer and length
+ /// @param hwaddr pointer to hardware address
+ /// @param len length of the address pointed by hwaddr
+ /// @param htype hardware type
+ HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype);
+
+ /// @brief constructor, based on C++ vector<uint8_t>
+ /// @param hwaddr const reference to hardware address
+ /// @param htype hardware type
+ HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype);
+
+ // Vector that keeps the actual hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ // Hardware type
+ uint8_t htype_;
+
+ /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
+ std::string toText() const;
+
+ /// @brief Compares two hardware addresses for equality
+ bool operator==(const HWAddr& other) const;
+
+ /// @brief Compares two hardware addresses for inequality
+ bool operator!=(const HWAddr& other) const;
+};
+
+/// @brief Shared pointer to a hardware address structure
+typedef boost::shared_ptr<HWAddr> HWAddrPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcp/iface_mgr.cc b/src/lib/dhcp/iface_mgr.cc
index 2f1199e..a4f4659 100644
--- a/src/lib/dhcp/iface_mgr.cc
+++ b/src/lib/dhcp/iface_mgr.cc
@@ -212,8 +212,8 @@ bool IfaceMgr::openSockets4(const uint16_t port) {
addr != addrs.end();
++addr) {
- // Skip IPv6 addresses
- if (addr->getFamily() != AF_INET) {
+ // Skip all but V4 addresses.
+ if (!addr->isV4()) {
continue;
}
@@ -247,8 +247,8 @@ bool IfaceMgr::openSockets6(const uint16_t port) {
addr != addrs.end();
++addr) {
- // skip IPv4 addresses
- if (addr->getFamily() != AF_INET6) {
+ // Skip all but V6 addresses.
+ if (!addr->isV6()) {
continue;
}
@@ -356,12 +356,13 @@ int IfaceMgr::openSocket(const std::string& ifname, const IOAddress& addr,
if (!iface) {
isc_throw(BadValue, "There is no " << ifname << " interface present.");
}
- switch (addr.getFamily()) {
- case AF_INET:
+ if (addr.isV4()) {
return openSocket4(*iface, addr, port);
- case AF_INET6:
+
+ } else if (addr.isV6()) {
return openSocket6(*iface, addr, port);
- default:
+
+ } else {
isc_throw(BadValue, "Failed to detect family of address: "
<< addr.toText());
}
@@ -469,7 +470,7 @@ IfaceMgr::getLocalAddress(const IOAddress& remote_addr, const uint16_t port) {
asio::error_code err_code;
// If remote address is broadcast address we have to
// allow this on the socket.
- if (remote_addr.getAddress().is_v4() &&
+ if (remote_addr.isV4() &&
(remote_addr == IOAddress(DHCP_IPV4_BROADCAST_ADDRESS))) {
// Socket has to be open prior to setting the broadcast
// option. Otherwise set_option will complain about
@@ -556,9 +557,7 @@ int IfaceMgr::openSocket6(Iface& iface, const IOAddress& addr, uint16_t port) {
addr6.sin6_scope_id = if_nametoindex(iface.getName().c_str());
}
- memcpy(&addr6.sin6_addr,
- addr.getAddress().to_v6().to_bytes().data(),
- sizeof(addr6.sin6_addr));
+ memcpy(&addr6.sin6_addr, &addr.toBytes()[0], sizeof(addr6.sin6_addr));
#ifdef HAVE_SA_LEN
addr6.sin6_len = sizeof(addr6);
#endif
@@ -660,7 +659,7 @@ IfaceMgr::send(const Pkt6Ptr& pkt) {
to.sin6_family = AF_INET6;
to.sin6_port = htons(pkt->getRemotePort());
memcpy(&to.sin6_addr,
- pkt->getRemoteAddr().getAddress().to_v6().to_bytes().data(),
+ &pkt->getRemoteAddr().toBytes()[0],
16);
to.sin6_scope_id = pkt->getIndex();
@@ -798,7 +797,7 @@ IfaceMgr::receive4(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */) {
s != socket_collection.end(); ++s) {
// Only deal with IPv4 addresses.
- if (s->addr_.getFamily() == AF_INET) {
+ if (s->addr_.isV4()) {
names << s->sockfd_ << "(" << iface->getName() << ") ";
// Add this socket to listening set
@@ -950,8 +949,8 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
for (SocketCollection::const_iterator s = socket_collection.begin();
s != socket_collection.end(); ++s) {
- // Only deal with IPv4 addresses.
- if (s->addr_.getFamily() == AF_INET6) {
+ // Only deal with IPv6 addresses.
+ if (s->addr_.isV6()) {
names << s->sockfd_ << "(" << iface->getName() << ") ";
// Add this socket to listening set
@@ -1098,9 +1097,9 @@ Pkt6Ptr IfaceMgr::receive6(uint32_t timeout_sec, uint32_t timeout_usec /* = 0 */
pkt->updateTimestamp();
- pkt->setLocalAddr(IOAddress::from_bytes(AF_INET6,
+ pkt->setLocalAddr(IOAddress::fromBytes(AF_INET6,
reinterpret_cast<const uint8_t*>(&to_addr)));
- pkt->setRemoteAddr(IOAddress::from_bytes(AF_INET6,
+ pkt->setRemoteAddr(IOAddress::fromBytes(AF_INET6,
reinterpret_cast<const uint8_t*>(&from.sin6_addr)));
pkt->setRemotePort(ntohs(from.sin6_port));
pkt->setIndex(ifindex);
diff --git a/src/lib/dhcp/iface_mgr_linux.cc b/src/lib/dhcp/iface_mgr_linux.cc
index 38189aa..e7d5048 100644
--- a/src/lib/dhcp/iface_mgr_linux.cc
+++ b/src/lib/dhcp/iface_mgr_linux.cc
@@ -302,7 +302,7 @@ void Netlink::ipaddrs_get(IfaceMgr::Iface& iface, NetlinkMessages& addr_info) {
memcpy(addr, RTA_DATA(rta_tb[IFLA_ADDRESS]),
ifa->ifa_family==AF_INET?V4ADDRESS_LEN:V6ADDRESS_LEN);
- IOAddress a = IOAddress::from_bytes(ifa->ifa_family, addr);
+ IOAddress a = IOAddress::fromBytes(ifa->ifa_family, addr);
iface.addAddress(a);
/// TODO: Read lifetimes of configured IPv6 addresses
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index 7fb88bf..a3921cc 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -20,8 +20,9 @@
#include <dhcp/option.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int_array.h>
#include <dhcp/option_definition.h>
+#include <dhcp/option_int_array.h>
+#include <dhcp/std_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -45,17 +46,62 @@ OptionDefContainer LibDHCP::v4option_defs_;
OptionDefContainer LibDHCP::v6option_defs_;
const OptionDefContainer&
-LibDHCP::getOptionDefs(Option::Universe u) {
+LibDHCP::getOptionDefs(const Option::Universe u) {
switch (u) {
case Option::V4:
+ if (v4option_defs_.empty()) {
+ initStdOptionDefs4();
+ }
return (v4option_defs_);
case Option::V6:
+ if (v6option_defs_.empty()) {
+ initStdOptionDefs6();
+ }
return (v6option_defs_);
default:
isc_throw(isc::BadValue, "invalid universe " << u << " specified");
}
}
+OptionDefinitionPtr
+LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
+ const OptionDefContainer& defs = getOptionDefs(u);
+ const OptionDefContainerTypeIndex& idx = defs.get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(code);
+ if (range.first != range.second) {
+ return (*range.first);
+ }
+ return (OptionDefinitionPtr());
+}
+
+bool
+LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
+ if (u == Option::V6) {
+ if (code < 79 &&
+ code != 10 &&
+ code != 35) {
+ return (true);
+ }
+
+ } else if (u == Option::V4) {
+ if (!(code == 84 ||
+ code == 96 ||
+ (code > 101 && code < 112) ||
+ code == 115 ||
+ code == 126 ||
+ code == 127 ||
+ (code > 146 && code < 150) ||
+ (code > 177 && code < 208) ||
+ (code > 213 && code < 220) ||
+ (code > 221 && code < 224))) {
+ return (true);
+ }
+
+ }
+
+ return (false);
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
@@ -84,42 +130,59 @@ LibDHCP::optionFactory(Option::Universe u,
size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
- size_t end = buf.size();
-
- while (offset +4 <= end) {
- uint16_t opt_type = buf[offset] * 256 + buf[offset + 1];
+ size_t length = buf.size();
+
+ // Get the list of stdandard option definitions.
+ const OptionDefContainer& option_defs = LibDHCP::getOptionDefs(Option::V6);
+ // Get the search index #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+
+ // The buffer being read comprises a set of options, each starting with
+ // a two-byte type code and a two-byte length field.
+ while (offset + 4 <= length) {
+ uint16_t opt_type = isc::util::readUint16(&buf[offset]);
offset += 2;
- uint16_t opt_len = buf[offset] * 256 + buf[offset + 1];
+ uint16_t opt_len = isc::util::readUint16(&buf[offset]);
offset += 2;
- if (offset + opt_len > end) {
+ if (offset + opt_len > length) {
// @todo: consider throwing exception here.
return (offset);
}
+
+ // Get all definitions with the particular option code. Note that option
+ // code is non-unique within this container however at this point we
+ // expect to get one option definition with the particular code. If more
+ // are returned we report an error.
+ const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
+ // Get the number of returned option definitions for the option code.
+ size_t num_defs = distance(range.first, range.second);
+
OptionPtr opt;
- switch (opt_type) {
- case D6O_IA_NA:
- case D6O_IA_PD:
- opt = OptionPtr(new Option6IA(opt_type,
- buf.begin() + offset,
- buf.begin() + offset + opt_len));
- break;
- case D6O_IAADDR:
- opt = OptionPtr(new Option6IAAddr(opt_type,
- buf.begin() + offset,
- buf.begin() + offset + opt_len));
- break;
- case D6O_ORO:
- opt = OptionPtr(new Option6IntArray<uint16_t>(opt_type,
- buf.begin() + offset,
- buf.begin() + offset + opt_len));
- break;
- default:
- opt = OptionPtr(new Option(Option::V6,
- opt_type,
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+ " for option type " << opt_type << " returned. Currently it is not"
+ " supported to initialize multiple option definitions"
+ " for the same option code. This will be supported once"
+ " support for option spaces is implemented");
+ } else if (num_defs == 0) {
+ // @todo Don't crash if definition does not exist because only a few
+ // option definitions are initialized right now. In the future
+ // we will initialize definitions for all options and we will
+ // remove this elseif. For now, return generic option.
+ opt = OptionPtr(new Option(Option::V6, opt_type,
buf.begin() + offset,
buf.begin() + offset + opt_len));
- break;
+ } else {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ assert(def);
+ opt = def->optionFactory(Option::V6, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
}
// add option to options
options.insert(std::make_pair(opt_type, opt));
@@ -133,7 +196,14 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
isc::dhcp::Option::OptionCollection& options) {
size_t offset = 0;
- // 2 byte - header of DHCPv4 option
+ // Get the list of stdandard option definitions.
+ const OptionDefContainer& option_defs = LibDHCP::getOptionDefs(Option::V4);
+ // Get the search index #1. It allows to search for option definitions
+ // using option code.
+ const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
+
+ // The buffer being read comprises a set of options, each starting with
+ // a one-byte type code and a one-byte length field.
while (offset + 1 <= buf.size()) {
uint8_t opt_type = buf[offset++];
@@ -147,8 +217,10 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
continue;
if (offset + 1 >= buf.size()) {
+ // opt_type must be cast to integer so as it is not treated as
+ // unsigned char value (a number is presented in error message).
isc_throw(OutOfRange, "Attempt to parse truncated option "
- << opt_type);
+ << static_cast<int>(opt_type));
}
uint8_t opt_len = buf[offset++];
@@ -158,12 +230,35 @@ size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
<< "-byte long buffer.");
}
+ // Get all definitions with the particular option code. Note that option code
+ // is non-unique within this container however at this point we expect
+ // to get one option definition with the particular code. If more are
+ // returned we report an error.
+ const OptionDefContainerTypeRange& range = idx.equal_range(opt_type);
+ // Get the number of returned option definitions for the option code.
+ size_t num_defs = distance(range.first, range.second);
+
OptionPtr opt;
- switch(opt_type) {
- default:
+ if (num_defs > 1) {
+ // Multiple options of the same code are not supported right now!
+ isc_throw(isc::Unexpected, "Internal error: multiple option definitions"
+ " for option type " << static_cast<int>(opt_type)
+ << " returned. Currently it is not supported to initialize"
+ << " multiple option definitions for the same option code."
+ << " This will be supported once support for option spaces"
+ << " is implemented");
+ } else if (num_defs == 0) {
opt = OptionPtr(new Option(Option::V4, opt_type,
- buf.begin()+offset,
- buf.begin()+offset+opt_len));
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len));
+ } else {
+ // The option definition has been found. Use it to create
+ // the option instance from the provided buffer chunk.
+ const OptionDefinitionPtr& def = *(range.first);
+ assert(def);
+ opt = def->optionFactory(Option::V4, opt_type,
+ buf.begin() + offset,
+ buf.begin() + offset + opt_len);
}
options.insert(std::make_pair(opt_type, opt));
@@ -229,78 +324,58 @@ void LibDHCP::OptionFactoryRegister(Option::Universe u,
}
void
-LibDHCP::initStdOptionDefs(Option::Universe u) {
- switch (u) {
- case Option::V4:
- initStdOptionDefs4();
- break;
- case Option::V6:
- initStdOptionDefs6();
- break;
- default:
- isc_throw(isc::BadValue, "invalid universe " << u << " specified");
- }
-}
-
-void
LibDHCP::initStdOptionDefs4() {
- isc_throw(isc::NotImplemented, "initStdOptionDefs4 is not implemented");
+ v4option_defs_.clear();
+
+ // Now let's add all option definitions.
+ for (int i = 0; i < OPTION_DEF_PARAMS_SIZE4; ++i) {
+ OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS4[i].name,
+ OPTION_DEF_PARAMS4[i].code,
+ OPTION_DEF_PARAMS4[i].type,
+ OPTION_DEF_PARAMS4[i].array));
+
+ for (int rec = 0; rec < OPTION_DEF_PARAMS4[i].records_size; ++rec) {
+ definition->addRecordField(OPTION_DEF_PARAMS4[i].records[rec]);
+ }
+
+ // Sanity check if the option is valid.
+ try {
+ definition->validate();
+ } catch (const Exception& ex) {
+ // This is unlikely event that validation fails and may
+ // be only caused by programming error. To guarantee the
+ // data consistency we clear all option definitions that
+ // have been added so far and pass the exception forward.
+ v4option_defs_.clear();
+ throw;
+ }
+ v4option_defs_.push_back(definition);
+ }
}
void
LibDHCP::initStdOptionDefs6() {
v6option_defs_.clear();
- struct OptionParams {
- std::string name;
- uint16_t code;
- OptionDefinition::DataType type;
- bool array;
- };
- OptionParams params[] = {
- { "CLIENTID", D6O_CLIENTID, OptionDefinition::BINARY_TYPE, false },
- { "SERVERID", D6O_SERVERID, OptionDefinition::BINARY_TYPE, false },
- { "IA_NA", D6O_IA_NA, OptionDefinition::RECORD_TYPE, false },
- { "IAADDR", D6O_IAADDR, OptionDefinition::RECORD_TYPE, false },
- { "ORO", D6O_ORO, OptionDefinition::UINT16_TYPE, true },
- { "ELAPSED_TIME", D6O_ELAPSED_TIME, OptionDefinition::UINT16_TYPE, false },
- { "STATUS_CODE", D6O_STATUS_CODE, OptionDefinition::RECORD_TYPE, false },
- { "RAPID_COMMIT", D6O_RAPID_COMMIT, OptionDefinition::EMPTY_TYPE, false },
- { "DNS_SERVERS", D6O_NAME_SERVERS, OptionDefinition::IPV6_ADDRESS_TYPE, true }
- };
- const int params_size = sizeof(params) / sizeof(params[0]);
-
- for (int i = 0; i < params_size; ++i) {
- OptionDefinitionPtr definition(new OptionDefinition(params[i].name,
- params[i].code,
- params[i].type,
- params[i].array));
- switch(params[i].code) {
- case D6O_IA_NA:
- for (int j = 0; j < 3; ++j) {
- definition->addRecordField(OptionDefinition::UINT32_TYPE);
- }
- break;
- case D6O_IAADDR:
- definition->addRecordField(OptionDefinition::IPV6_ADDRESS_TYPE);
- definition->addRecordField(OptionDefinition::UINT32_TYPE);
- definition->addRecordField(OptionDefinition::UINT32_TYPE);
- break;
- case D6O_STATUS_CODE:
- definition->addRecordField(OptionDefinition::UINT16_TYPE);
- definition->addRecordField(OptionDefinition::STRING_TYPE);
- break;
- default:
- // The default case is intentionally left empty
- // as it does not need any processing.
- ;
+ for (int i = 0; i < OPTION_DEF_PARAMS_SIZE6; ++i) {
+ OptionDefinitionPtr definition(new OptionDefinition(OPTION_DEF_PARAMS6[i].name,
+ OPTION_DEF_PARAMS6[i].code,
+ OPTION_DEF_PARAMS6[i].type,
+ OPTION_DEF_PARAMS6[i].array));
+
+ for (int rec = 0; rec < OPTION_DEF_PARAMS6[i].records_size; ++rec) {
+ definition->addRecordField(OPTION_DEF_PARAMS6[i].records[rec]);
}
+
try {
definition->validate();
} catch (const Exception& ex) {
- isc_throw(isc::Unexpected, "internal server error: invalid definition of standard"
- << " DHCPv6 option (with code " << params[i].code << "): "
- << ex.what());
+ // This is unlikely event that validation fails and may
+ // be only caused by programming error. To guarantee the
+ // data consistency we clear all option definitions that
+ // have been added so far and pass the exception forward.
+ v6option_defs_.clear();
+ throw;
}
v6option_defs_.push_back(definition);
}
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c773fd7..bc47405 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -33,9 +33,42 @@ public:
/// @brief Return collection of option definitions.
///
+ /// Method returns the collection of DHCP standard DHCP
+ /// option definitions.
+ /// @todo DHCPv4 option definitions are not implemented. For now
+ /// this function will throw isc::NotImplemented in case of attempt
+ /// to get option definitions for V4 universe.
+ ///
/// @param u universe of the options (V4 or V6).
+ ///
/// @return collection of option definitions.
- static const OptionDefContainer& getOptionDefs(Option::Universe u);
+ static const OptionDefContainer& getOptionDefs(const Option::Universe u);
+
+ /// @brief Return the first option definition matching a
+ /// particular option code.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return reference to an option definition being requested
+ /// or NULL pointer if option definition has not been found.
+ static OptionDefinitionPtr getOptionDef(const Option::Universe u,
+ const uint16_t code);
+
+ /// @brief Check if the specified option is a standard option.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return true if the specified option is a standard option.
+ /// @todo We arleady create option definitions for the subset if
+ /// standard options. We are aiming that this function checks
+ /// the presence of the standard option definition and if it finds
+ /// it, then the true value is returned. However, at this point
+ /// this is not doable because some of the definitions (for less
+ /// important options) are not created yet.
+ static bool isStandardOption(const Option::Universe u,
+ const uint16_t code);
/// @brief Factory function to create instance of option.
///
@@ -113,21 +146,6 @@ public:
uint16_t type,
Option::Factory * factory);
- /// Initialize standard DHCP options (V4 or V6).
- ///
- /// The method creates option definitions for all options
- /// (DHCPv4 or DHCPv6 depending on universe specified).
- /// Currently DHCPv4 option definitions initialization is not
- /// implemented thus this function will throw isc::NotImplemented
- /// if V4 universe is specified.
- ///
- /// @param u universe
- /// @throw isc::Unexpected if internal error occured during option
- /// definitions creation.
- /// @throw std::bad_alloc if system went out of memory.
- /// @throw isc::NotImplemented when V4 universe specified.
- static void initStdOptionDefs(Option::Universe u);
-
private:
/// Initialize standard DHCPv4 option definitions.
@@ -135,18 +153,18 @@ private:
/// The method creates option definitions for all DHCPv4 options.
/// Currently this function is not implemented.
///
- /// @todo implemend this function.
- ///
- /// @throw isc::NotImplemeneted
+ /// @throw std::bad alloc if system went out of memory.
+ /// @throw MalformedOptionDefinition if any of the definitions
+ /// are incorrect. This is programming error.
static void initStdOptionDefs4();
/// Initialize standard DHCPv6 option definitions.
///
/// The method creates option definitions for all DHCPv6 options.
///
- /// @throw isc::Unexpected if internal error occured during option
- /// definitions creation.
/// @throw std::bad_alloc if system went out of memory.
+ /// @throw MalformedOptionDefinition if any of the definitions
+ /// is incorrect. This is a programming error.
static void initStdOptionDefs6();
/// pointers to factories that produce DHCPv6 options
diff --git a/src/lib/dhcp/libdhcsrv.dox b/src/lib/dhcp/libdhcsrv.dox
deleted file mode 100644
index bb4a8ec..0000000
--- a/src/lib/dhcp/libdhcsrv.dox
+++ /dev/null
@@ -1,86 +0,0 @@
-/**
- @page libdhcpsrv libdhcpsrv - Server DHCP library
-
-This library contains code useful for DHCPv4 and DHCPv6 server operations, like
-Lease Manager that stores leases information, configuration manager that stores
-configuration etc. The code here is server specific. For generic (useful in
-server, client, relay and other tools like perfdhcp) code, please see
-\ref libdhcp.
-
-This library contains several crucial elements of the DHCP server operation:
-
-- isc::dhcp::LeaseMgr - Lease Manager is a name for database backend that stores
- leases.
-- isc::dhcp::CfgMgr - Configuration Manager that holds DHCP specific
- configuration information (subnets, pools, options, timer values etc.) in
- easy to use format.
-- AllocEngine - allocation engine that handles new requestes and allocates new
- leases.
-
- at section leasemgr Lease Manager
-
-LeaseMgr provides a common, unified abstract API for all database backends. All
-backends are derived from the base class isc::dhcp::LeaseMgr. Currently the
-only available backend is MySQL (see \ref isc::dhcp::MySqlLeaseMgr).
-
- at section cfgmgr Configuration Manager
-
-Configuration Manager (\ref isc::dhcp::CfgMgr) stores configuration information
-necessary for DHCPv4 and DHCPv6 server operation. In particular, it stores
-subnets (\ref isc::dhcp::Subnet4 and \ref isc::dhcp::Subnet6) together with
-their pools (\ref isc::dhcp::Pool4 and \ref isc::dhcp::Pool6), options and
-other information specified by the used in BIND10 configuration.
-
- at section allocengine Allocation Engine
-
-Allocation Engine (\ref isc::dhcp::AllocEngine) is what its name say - an engine
-that handles allocation of new leases. It takes parameters that the client
-provided (client-id, DUID, subnet, a hint if the user provided one, etc.) and
-then attempts to allocate a lease.
-
-There is no single best soluction to the address assignment problem. Server
-is expected to pick an address from its available pools is currently not used.
-There are many possible algorithms that can do that, each with its own advantages
-and drawbacks. This allocation engine must provide robust operation is radically
-different scenarios, so there address selection problem was abstracted into
-separate module, called allocator. Its sole purpose is to pick an address from
-a pool. Allocation engine will then check if the picked address is free and if
-it is not, then will ask allocator to pick again.
-
-At lease 3 allocators will be implemented:
-
-- Iterative - it iterates over all addresses in available pools, one
-by one. The advantages of this approach are speed (typically it only needs to
-increase last address), the guarantee to cover all addresses and predictability.
-This allocator behaves very good in case of nearing depletion. Even when pools
-are almost completely allocated, it still will be able to allocate outstanding
-leases efficiently. Predictability can also be considered a serious flaw in
-some environments, as prediction of the next address is trivial and can be
-leveraged by an attacker. Another drawback of this allocator is that it does
-not attempt to give the same address to returning clients (clients that released
-or expired their leases and are requesting a new lease will likely to get a
-different lease). This allocator is implemented in \ref isc::dhcp::AllocEngine::IterativeAllocator.
-
-- Hashed - ISC-DHCP uses hash of the client-id or DUID to determine, which
-address is tried first. If that address is not available, the result is hashed
-again. That procedure is repeated until available address is found or there
-are no more addresses left. The benefit of that approach is that it provides
-a relative lease stability, so returning old clients are likely to get the same
-address again. The drawbacks are increased computation cost, as each iteration
-requires use of a hashing function. That is especially difficult when the
-pools are almost depleted. It also may be difficult to guarantee that the
-repeated hashing will iterate over all available addresses in all pools. Flawed
-hash algorithm can go into cycles that iterate over only part of the addresses.
-It is difficult to detect such issues as only some initial seed (client-id
-or DUID) values may trigger short cycles. This allocator is currently not
-implemented.
-
-- Random - Another possible approach to address selection is randomization. This
-allocator can pick an address randomly from the configured pool. The benefit
-of this approach is that it is easy to implement and makes attacks based on
-address prediction more difficult. The drawback of this approach is that
-returning clients are almost guaranteed to get a different address. Another
-drawback is that with almost depleted pools it is increasingly difficult to
-"guess" an address that is free. This allocator is currently not implemented.
-
-*/
\ No newline at end of file
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index d2be1d2..dbdac0c 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -62,14 +62,14 @@ Option::Option(Universe u, uint16_t type, OptionBufferConstIter first,
void
Option::check() {
if ( (universe_ != V4) && (universe_ != V6) ) {
- isc_throw(BadValue, "Invalid universe type specified."
+ isc_throw(BadValue, "Invalid universe type specified. "
<< "Only V4 and V6 are allowed.");
}
if (universe_ == V4) {
if (type_ > 255) {
- isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big."
+ isc_throw(OutOfRange, "DHCPv4 Option type " << type_ << " is too big. "
<< "For DHCPv4 allowed type range is 0..255");
} else if (data_.size() > 255) {
isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big.");
@@ -87,20 +87,55 @@ void Option::pack(isc::util::OutputBuffer& buf) {
switch (universe_) {
case V6:
return (pack6(buf));
+
case V4:
return (pack4(buf));
+
default:
- isc_throw(BadValue, "Failed to pack " << type_ << " option. Do not "
- << "use this method for options other than DHCPv6.");
+ isc_throw(BadValue, "Failed to pack " << type_ << " option as the "
+ << "universe type is unknown.");
}
}
void
Option::pack4(isc::util::OutputBuffer& buf) {
- switch (universe_) {
- case V4: {
+ if (universe_ == V4) {
+ // Write a header.
+ packHeader(buf);
+ // Write data.
+ if (!data_.empty()) {
+ buf.writeData(&data_[0], data_.size());
+ }
+ // Write sub-options.
+ packOptions(buf);
+ } else {
+ isc_throw(BadValue, "Invalid universe type " << universe_);
+ }
+
+ return;
+}
+
+void Option::pack6(isc::util::OutputBuffer& buf) {
+ if (universe_ == V6) {
+ // Write a header.
+ packHeader(buf);
+ // Write data.
+ if (!data_.empty()) {
+ buf.writeData(&data_[0], data_.size());
+ }
+ // Write sub-options.
+ packOptions(buf);
+ } else {
+ isc_throw(BadValue, "Invalid universe type " << universe_);
+ }
+ return;
+}
+
+void
+Option::packHeader(isc::util::OutputBuffer& buf) {
+ if (universe_ == V4) {
if (len() > 255) {
- isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big."
+ isc_throw(OutOfRange, "DHCPv4 Option " << type_ << " is too big. "
<< "At most 255 bytes are supported.");
/// TODO Larger options can be stored as separate instances
/// of DHCPv4 options. Clients MUST concatenate them.
@@ -110,39 +145,45 @@ Option::pack4(isc::util::OutputBuffer& buf) {
buf.writeUint8(type_);
buf.writeUint8(len() - getHeaderLen());
- buf.writeData(&data_[0], data_.size());
-
- LibDHCP::packOptions(buf, options_);
- return;
- }
- case V6:
- /// TODO: Do we need a sanity check for option size here?
+ } else {
buf.writeUint16(type_);
buf.writeUint16(len() - getHeaderLen());
+ }
+}
+void
+Option::packOptions(isc::util::OutputBuffer& buf) {
+ switch (universe_) {
+ case V4:
LibDHCP::packOptions(buf, options_);
return;
+ case V6:
+ LibDHCP::packOptions6(buf, options_);
+ return;
default:
- isc_throw(OutOfRange, "Invalid universe type" << universe_);
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
}
}
-void Option::pack6(isc::util::OutputBuffer& buf) {
- buf.writeUint16(type_);
- buf.writeUint16(len() - getHeaderLen());
-
- if (! data_.empty()) {
- buf.writeData(&data_[0], data_.size());
- }
-
- return LibDHCP::packOptions6(buf, options_);
-}
-
void Option::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
data_ = OptionBuffer(begin, end);
}
+void
+Option::unpackOptions(const OptionBuffer& buf) {
+ switch (universe_) {
+ case V4:
+ LibDHCP::unpackOptions4(buf, options_);
+ return;
+ case V6:
+ LibDHCP::unpackOptions6(buf, options_);
+ return;
+ default:
+ isc_throw(isc::BadValue, "Invalid universe type " << universe_);
+ }
+}
+
uint16_t Option::len() {
// Returns length of the complete option (data length + DHCPv4/DHCPv6
// option header)
@@ -286,6 +327,10 @@ void Option::setData(const OptionBufferConstIter first,
std::copy(first, last, data_.begin());
}
+bool Option::equal(const OptionPtr& other) const {
+ return ( (getType() == other->getType()) &&
+ (getData() == other->getData()) );
+}
Option::~Option() {
diff --git a/src/lib/dhcp/option.h b/src/lib/dhcp/option.h
index 920ef13..e4105cc 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -119,7 +119,7 @@ public:
/// This constructor takes vector<uint8_t>& which is used in cases
/// when content of the option will be copied and stored within
/// option object. V4 Options follow that approach already.
- /// TODO Migrate V6 options to that approach.
+ /// @todo Migrate V6 options to that approach.
///
/// @param u specifies universe (V4 or V6)
/// @param type option type (0-255 for V4 and 0-65535 for V6)
@@ -131,7 +131,7 @@ public:
/// This contructor is similar to the previous one, but it does not take
/// the whole vector<uint8_t>, but rather subset of it.
///
- /// TODO: This can be templated to use different containers, not just
+ /// @todo This can be templated to use different containers, not just
/// vector. Prototype should look like this:
/// template<typename InputIterator> Option(Universe u, uint16_t type,
/// InputIterator first, InputIterator last);
@@ -152,7 +152,7 @@ public:
/// @brief returns option universe (V4 or V6)
///
/// @return universe type
- Universe getUniverse() { return universe_; };
+ Universe getUniverse() const { return universe_; };
/// @brief Writes option in wire-format to a buffer.
///
@@ -160,19 +160,24 @@ public:
/// byte after stored option (that is useful for writing options one after
/// another). Used in DHCPv6 options.
///
- /// TODO: Migrate DHCPv6 code to pack(OutputBuffer& buf) version
+ /// @todo Migrate DHCPv6 code to pack(OutputBuffer& buf) version
///
/// @param buf pointer to a buffer
+ ///
+ /// @throw BadValue Universe of the option is neither V4 nor V6.
virtual void pack(isc::util::OutputBuffer& buf);
/// @brief Writes option in a wire-format to a buffer.
///
/// Method will throw if option storing fails for some reason.
///
- /// TODO Once old (DHCPv6) implementation is rewritten,
+ /// @todo Once old (DHCPv6) implementation is rewritten,
/// unify pack4() and pack6() and rename them to just pack().
///
/// @param buf output buffer (option will be stored there)
+ ///
+ /// @throw OutOfRange Option type is greater than 255.
+ /// @throw BadValue Universe is not V4.
virtual void pack4(isc::util::OutputBuffer& buf);
/// @brief Parses received buffer.
@@ -192,7 +197,7 @@ public:
/// Returns option type (0-255 for DHCPv4, 0-65535 for DHCPv6)
///
/// @return option type
- uint16_t getType() { return (type_); }
+ uint16_t getType() const { return (type_); }
/// Returns length of the complete option (data length + DHCPv4/DHCPv6
/// option header)
@@ -214,7 +219,7 @@ public:
///
/// @return pointer to actual data (or reference to an empty vector
/// if there is no data)
- virtual const OptionBuffer& getData() { return (data_); }
+ virtual const OptionBuffer& getData() const { return (data_); }
/// Adds a sub-option.
///
@@ -298,13 +303,73 @@ public:
/// just to force that every option has virtual dtor
virtual ~Option();
+ /// @brief Checks if two options are equal
+ ///
+ /// Equality verifies option type and option content. Care should
+ /// be taken when using this method. Implementation for derived
+ /// classes should be provided when this method is expected to be
+ /// used. It is safe in general, as the first check (different types)
+ /// will detect differences between base Option and derived
+ /// objects.
+ ///
+ /// @param other the other option
+ /// @return true if both options are equal
+ virtual bool equal(const OptionPtr& other) const;
+
protected:
/// Builds raw (over-wire) buffer of this option, including all
/// defined suboptions. Version for building DHCPv4 options.
///
/// @param buf output buffer (built options will be stored here)
+ ///
+ /// @throw BadValue Universe is not V6.
virtual void pack6(isc::util::OutputBuffer& buf);
+ /// @brief Store option's header in a buffer.
+ ///
+ /// This method writes option's header into a buffer in the
+ /// on-wire format. The universe set for the particular option
+ /// is used to determine whether option code and length are
+ /// stored as 2-byte (for DHCPv6) or single-byte (for DHCPv4)
+ /// values. For DHCPv4 options, this method checks if the
+ /// length does not exceed 255 bytes and throws exception if
+ /// it does.
+ /// This method is used by derived classes to pack option's
+ /// header into a buffer. This method should not be called
+ /// directly by other classes.
+ ///
+ /// @param [out] buf output buffer.
+ void packHeader(isc::util::OutputBuffer& buf);
+
+ /// @brief Store sub options in a buffer.
+ ///
+ /// This method stores all sub-options defined for a particular
+ /// option in a on-wire format in output buffer provided.
+ /// This function is called by pack function in this class or
+ /// derived classes that override pack.
+ ///
+ /// @param [out] buf output buffer.
+ ///
+ /// @todo The set of exceptions thrown by this function depend on
+ /// exceptions thrown by pack methods invoked on objects
+ /// representing sub options. We should consider whether to aggregate
+ /// those into one exception which can be documented here.
+ void packOptions(isc::util::OutputBuffer& buf);
+
+ /// @brief Builds a collection of sub options from the buffer.
+ ///
+ /// This method parses the provided buffer and builds a collection
+ /// of objects representing sub options. This function may throw
+ /// different exceptions when option assembly fails.
+ ///
+ /// @param buf buffer to be parsed.
+ ///
+ /// @todo The set of exceptions thrown by this function depend on
+ /// exceptions thrown by unpack methods invoked on objects
+ /// representing sub options. We should consider whether to aggregate
+ /// those into one exception which can be documented here.
+ void unpackOptions(const OptionBuffer& buf);
+
/// @brief A private method used for option correctness.
///
/// It is used in constructors. In there are any problems detected
@@ -324,7 +389,7 @@ protected:
/// collection for storing suboptions
OptionCollection options_;
- /// TODO: probably 2 different containers have to be used for v4 (unique
+ /// @todo probably 2 different containers have to be used for v4 (unique
/// options) and v6 (options with the same type can repeat)
};
diff --git a/src/lib/dhcp/option4_addrlst.cc b/src/lib/dhcp/option4_addrlst.cc
index 76341b2..86da9f6 100644
--- a/src/lib/dhcp/option4_addrlst.cc
+++ b/src/lib/dhcp/option4_addrlst.cc
@@ -86,7 +86,7 @@ Option4AddrLst::pack4(isc::util::OutputBuffer& buf) {
}
void Option4AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
- if (addr.getFamily() != AF_INET) {
+ if (!addr.isV4()) {
isc_throw(BadValue, "Can't store non-IPv4 address in "
<< "Option4AddrLst option");
}
@@ -107,7 +107,7 @@ void Option4AddrLst::setAddresses(const AddressContainer& addrs) {
void Option4AddrLst::addAddress(const isc::asiolink::IOAddress& addr) {
- if (addr.getFamily() != AF_INET) {
+ if (!addr.isV4()) {
isc_throw(BadValue, "Can't store non-IPv4 address in "
<< "Option4AddrLst option");
}
diff --git a/src/lib/dhcp/option4_addrlst.h b/src/lib/dhcp/option4_addrlst.h
index b266cbf..927f75b 100644
--- a/src/lib/dhcp/option4_addrlst.h
+++ b/src/lib/dhcp/option4_addrlst.h
@@ -111,10 +111,10 @@ public:
/// We return a copy of our list. Although this includes overhead,
/// it also makes this list safe to use after this option object
/// is no longer available. As options are expected to hold only
- /// a couple (1-3) addresses, the overhead is not that big.
+ /// a few (1-3) addresses, the overhead is not that big.
///
/// @return address container with addresses
- AddressContainer getAddresses() { return addrs_; };
+ AddressContainer getAddresses() const { return addrs_; };
/// @brief Sets addresses list.
///
diff --git a/src/lib/dhcp/option6_addrlst.cc b/src/lib/dhcp/option6_addrlst.cc
index 1af10b5..cb14070 100644
--- a/src/lib/dhcp/option6_addrlst.cc
+++ b/src/lib/dhcp/option6_addrlst.cc
@@ -49,7 +49,7 @@ Option6AddrLst::Option6AddrLst(uint16_t type, OptionBufferConstIter begin,
void
Option6AddrLst::setAddress(const isc::asiolink::IOAddress& addr) {
- if (addr.getFamily() != AF_INET6) {
+ if (!addr.isV6()) {
isc_throw(BadValue, "Can't store non-IPv6 address in Option6AddrLst option");
}
@@ -72,7 +72,13 @@ void Option6AddrLst::pack(isc::util::OutputBuffer& buf) {
for (AddressContainer::const_iterator addr=addrs_.begin();
addr!=addrs_.end(); ++addr) {
- buf.writeData(addr->getAddress().to_v6().to_bytes().data(), V6ADDRESS_LEN);
+ if (!addr->isV6()) {
+ isc_throw(isc::BadValue, addr->toText()
+ << " is not an IPv6 address");
+ }
+ // If an address is IPv6 address it should have assumed
+ // length of V6ADDRESS_LEN.
+ buf.writeData(&addr->toBytes()[0], V6ADDRESS_LEN);
}
}
@@ -84,7 +90,7 @@ void Option6AddrLst::unpack(OptionBufferConstIter begin,
<< " is not divisible by 16.");
}
while (begin != end) {
- addrs_.push_back(IOAddress::from_bytes(AF_INET6, &(*begin)));
+ addrs_.push_back(IOAddress::fromBytes(AF_INET6, &(*begin)));
begin += V6ADDRESS_LEN;
}
}
@@ -104,8 +110,7 @@ std::string Option6AddrLst::toText(int indent /* =0 */) {
}
uint16_t Option6AddrLst::len() {
-
- return (OPTION6_HDR_LEN + addrs_.size()*V6ADDRESS_LEN);
+ return (OPTION6_HDR_LEN + addrs_.size() * V6ADDRESS_LEN);
}
} // end of namespace isc::dhcp
diff --git a/src/lib/dhcp/option6_addrlst.h b/src/lib/dhcp/option6_addrlst.h
index b9c0deb..8327201 100644
--- a/src/lib/dhcp/option6_addrlst.h
+++ b/src/lib/dhcp/option6_addrlst.h
@@ -82,10 +82,10 @@ public:
/// We return a copy of our list. Although this includes overhead,
/// it also makes this list safe to use after this option object
/// is no longer available. As options are expected to hold only
- /// a couple (1-3) addresses, the overhead is not that big.
+ /// a few (1-3) addresses, the overhead is not that big.
///
/// @return address container with addresses
- AddressContainer getAddresses() { return addrs_; };
+ AddressContainer getAddresses() const { return addrs_; };
// returns data length (data length + DHCPv4/DHCPv6 option header)
virtual uint16_t len();
diff --git a/src/lib/dhcp/option6_ia.cc b/src/lib/dhcp/option6_ia.cc
index 9e4e01e..64c2936 100644
--- a/src/lib/dhcp/option6_ia.cc
+++ b/src/lib/dhcp/option6_ia.cc
@@ -44,7 +44,7 @@ void Option6IA::pack(isc::util::OutputBuffer& buf) {
buf.writeUint32(t1_);
buf.writeUint32(t2_);
- LibDHCP::packOptions6(buf, options_);
+ packOptions(buf);
}
void Option6IA::unpack(OptionBufferConstIter begin,
@@ -62,7 +62,7 @@ void Option6IA::unpack(OptionBufferConstIter begin,
t2_ = readUint32( &(*begin) );
begin += sizeof(uint32_t);
- LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+ unpackOptions(OptionBuffer(begin, end));
}
std::string Option6IA::toText(int indent /* = 0*/) {
diff --git a/src/lib/dhcp/option6_ia.h b/src/lib/dhcp/option6_ia.h
index 54624d0..32f3667 100644
--- a/src/lib/dhcp/option6_ia.h
+++ b/src/lib/dhcp/option6_ia.h
@@ -68,12 +68,17 @@ public:
/// Sets T1 timer.
///
/// @param t1 t1 value to be set
- void setT1(uint32_t t1) { t1_=t1; }
+ void setT1(uint32_t t1) { t1_ = t1; }
/// Sets T2 timer.
///
/// @param t2 t2 value to be set
- void setT2(uint32_t t2) { t2_=t2; }
+ void setT2(uint32_t t2) { t2_ = t2; }
+
+ /// Sets Identity Association Identifier.
+ ///
+ /// @param iaid IAID value to be set
+ void setIAID(uint32_t iaid) { iaid_ = iaid; }
/// Returns IA identifier.
///
diff --git a/src/lib/dhcp/option6_iaaddr.cc b/src/lib/dhcp/option6_iaaddr.cc
index b021ce9..d0ba087 100644
--- a/src/lib/dhcp/option6_iaaddr.cc
+++ b/src/lib/dhcp/option6_iaaddr.cc
@@ -51,15 +51,17 @@ void Option6IAAddr::pack(isc::util::OutputBuffer& buf) {
// length without 4-byte option header
buf.writeUint16(len() - getHeaderLen());
-
- buf.writeData(addr_.getAddress().to_v6().to_bytes().data(),
- isc::asiolink::V6ADDRESS_LEN);
+ if (!addr_.isV6()) {
+ isc_throw(isc::BadValue, addr_.toText()
+ << " is not an IPv6 address");
+ }
+ buf.writeData(&addr_.toBytes()[0], isc::asiolink::V6ADDRESS_LEN);
buf.writeUint32(preferred_);
buf.writeUint32(valid_);
// parse suboption (there shouldn't be any for IAADDR)
- LibDHCP::packOptions6(buf, options_);
+ packOptions(buf);
}
void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
@@ -69,7 +71,7 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
}
// 16 bytes: IPv6 address
- addr_ = IOAddress::from_bytes(AF_INET6, &(*begin));
+ addr_ = IOAddress::fromBytes(AF_INET6, &(*begin));
begin += V6ADDRESS_LEN;
preferred_ = readUint32( &(*begin) );
@@ -77,7 +79,8 @@ void Option6IAAddr::unpack(OptionBuffer::const_iterator begin,
valid_ = readUint32( &(*begin) );
begin += sizeof(uint32_t);
- LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
+
+ unpackOptions(OptionBuffer(begin, end));
}
std::string Option6IAAddr::toText(int indent /* =0 */) {
diff --git a/src/lib/dhcp/option6_int.h b/src/lib/dhcp/option6_int.h
deleted file mode 100644
index 2c51389..0000000
--- a/src/lib/dhcp/option6_int.h
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef OPTION6_INT_H
-#define OPTION6_INT_H
-
-#include <dhcp/libdhcp++.h>
-#include <dhcp/option.h>
-#include <dhcp/option_data_types.h>
-#include <util/io_utilities.h>
-
-#include <stdint.h>
-
-namespace isc {
-namespace dhcp {
-
-/// This template class represents DHCPv6 option with single value.
-/// This value is of integer type and can be any of the following:
-/// - uint8_t,
-/// - uint16_t,
-/// - uint32_t,
-/// - int8_t,
-/// - int16_t,
-/// - int32_t.
-///
-/// @param T data field type (see above).
-template<typename T>
-class Option6Int: public Option {
-
-public:
- /// @brief Constructor.
- ///
- /// @param type option type.
- /// @param value option value.
- ///
- /// @throw isc::dhcp::InvalidDataType if data field type provided
- /// as template parameter is not a supported integer type.
- Option6Int(uint16_t type, T value)
- : Option(Option::V6, type), value_(value) {
- if (!OptionDataTypes<T>::valid) {
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- }
-
- /// @brief Constructor.
- ///
- /// This constructor creates option from a buffer. This construtor
- /// may throw exception if \ref unpack function throws during buffer
- /// parsing.
- ///
- /// @param type option type.
- /// @param begin iterator to first byte of option data.
- /// @param end iterator to end of option data (first byte after option end).
- ///
- /// @throw isc::OutOfRange if provided buffer is shorter than data size.
- /// @throw isc::dhcp::InvalidDataType if data field type provided
- /// as template parameter is not a supported integer type.
- Option6Int(uint16_t type, OptionBufferConstIter begin,
- OptionBufferConstIter end)
- : Option(Option::V6, type) {
- if (!OptionDataTypes<T>::valid) {
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- unpack(begin, end);
- }
-
- /// Writes option in wire-format to buf, returns pointer to first unused
- /// byte after stored option.
- ///
- /// @param [out] buf buffer (option will be stored here)
- ///
- /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
- /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
- /// because it is checked in a constructor.
- void pack(isc::util::OutputBuffer& buf) {
- buf.writeUint16(type_);
- buf.writeUint16(len() - OPTION6_HDR_LEN);
- // Depending on the data type length we use different utility functions
- // writeUint16 or writeUint32 which write the data in the network byte
- // order to the provided buffer. The same functions can be safely used
- // for either unsiged or signed integers so there is not need to create
- // special cases for intX_t types.
- switch (OptionDataTypes<T>::len) {
- case 1:
- buf.writeUint8(value_);
- break;
- case 2:
- buf.writeUint16(value_);
- break;
- case 4:
- buf.writeUint32(value_);
- break;
- default:
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- LibDHCP::packOptions6(buf, options_);
- }
-
- /// @brief Parses received buffer
- ///
- /// Parses received buffer and returns offset to the first unused byte after
- /// parsed option.
- ///
- /// @param begin iterator to first byte of option data
- /// @param end iterator to end of option data (first byte after option end)
- ///
- /// @throw isc::OutOfRange if provided buffer is shorter than data size.
- /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
- /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
- /// because it is checked in a constructor.
- virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
- if (distance(begin, end) < sizeof(T)) {
- isc_throw(OutOfRange, "Option " << getType() << " truncated");
- }
- // @todo consider what to do if buffer is longer than data type.
-
- // Depending on the data type length we use different utility functions
- // readUint16 or readUint32 which read the data laid in the network byte
- // order from the provided buffer. The same functions can be safely used
- // for either unsiged or signed integers so there is not need to create
- // special cases for intX_t types.
- int data_size_len = OptionDataTypes<T>::len;
- switch (data_size_len) {
- case 1:
- value_ = *begin;
- break;
- case 2:
- value_ = isc::util::readUint16(&(*begin));
- break;
- case 4:
- value_ = isc::util::readUint32(&(*begin));
- break;
- default:
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- // Use local variable to set a new value for this iterator.
- // When using OptionDataTypes<T>::len directly some versions
- // of clang complain about unresolved reference to
- // OptionDataTypes structure during linking.
- begin += data_size_len;
- LibDHCP::unpackOptions6(OptionBuffer(begin, end), options_);
- }
-
- /// @brief Set option value.
- ///
- /// @param value new option value.
- void setValue(T value) { value_ = value; }
-
- /// @brief Return option value.
- ///
- /// @return option value.
- T getValue() const { return value_; }
-
- /// @brief returns complete length of option
- ///
- /// Returns length of this option, including option header and suboptions
- ///
- /// @return length of this option
- virtual uint16_t len() {
- uint16_t length = OPTION6_HDR_LEN + sizeof(T);
- // length of all suboptions
- for (Option::OptionCollection::iterator it = options_.begin();
- it != options_.end();
- ++it) {
- length += (*it).second->len();
- }
- return (length);
- }
-
-private:
-
- T value_; ///< Value conveyed by the option.
-};
-
-} // isc::dhcp namespace
-} // isc namespace
-
-#endif // OPTION6_INT_H
diff --git a/src/lib/dhcp/option6_int_array.h b/src/lib/dhcp/option6_int_array.h
deleted file mode 100644
index aba05a1..0000000
--- a/src/lib/dhcp/option6_int_array.h
+++ /dev/null
@@ -1,228 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef OPTION6_INT_ARRAY_H
-#define OPTION6_INT_ARRAY_H
-
-#include <dhcp/libdhcp++.h>
-#include <dhcp/option.h>
-#include <dhcp/option_data_types.h>
-#include <util/io_utilities.h>
-
-#include <stdint.h>
-
-namespace isc {
-namespace dhcp {
-
-/// This template class represents DHCPv6 option with array of
-/// integer values. The type of the elements in the array can be
-/// any of the following:
-/// - uint8_t,
-/// - uint16_t,
-/// - uint32_t,
-/// - int8_t,
-/// - int16_t,
-/// - int32_t.
-///
-/// @warning Since this option may convey variable number of integer
-/// values, sub-options are should not be added in this option as
-/// there is no way to distinguish them from other data. The API will
-/// allow addition of sub-options but they will be ignored during
-/// packing and unpacking option data.
-///
-/// @param T data field type (see above).
-template<typename T>
-class Option6IntArray: public Option {
-
-public:
-
- /// @brief Constructor.
- ///
- /// Creates option with empty values vector.
- ///
- /// @param type option type.
- ///
- /// @throw isc::dhcp::InvalidDataType if data field type provided
- /// as template parameter is not a supported integer type.
- Option6IntArray(uint16_t type)
- : Option(Option::V6, type),
- values_(0) {
- if (!OptionDataTypes<T>::valid) {
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- }
-
- /// @brief Constructor.
- ///
- /// @param type option type.
- /// @param buf buffer with option data (must not be empty).
- ///
- /// @throw isc::OutOfRange if provided buffer is empty or its length
- /// is not multiple of size of the data type in bytes.
- /// @throw isc::dhcp::InvalidDataType if data field type provided
- /// as template parameter is not a supported integer type.
- Option6IntArray(uint16_t type, const OptionBuffer& buf)
- : Option(Option::V6, type) {
- if (!OptionDataTypes<T>::valid) {
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- unpack(buf.begin(), buf.end());
- }
-
- /// @brief Constructor.
- ///
- /// This constructor creates option from a buffer. This construtor
- /// may throw exception if \ref unpack function throws during buffer
- /// parsing.
- ///
- /// @param type option type.
- /// @param begin iterator to first byte of option data.
- /// @param end iterator to end of option data (first byte after option end).
- ///
- /// @throw isc::OutOfRange if provided buffer is empty or its length
- /// is not multiple of size of the data type in bytes.
- /// @throw isc::dhcp::InvalidDataType if data field type provided
- /// as template parameter is not a supported integer type.
- Option6IntArray(uint16_t type, OptionBufferConstIter begin,
- OptionBufferConstIter end)
- : Option(Option::V6, type) {
- if (!OptionDataTypes<T>::valid) {
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- unpack(begin, end);
- }
-
- /// Writes option in wire-format to buf, returns pointer to first unused
- /// byte after stored option.
- ///
- /// @param [out] buf buffer (option will be stored here)
- ///
- /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
- /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
- /// because it is checked in a constructor.
- void pack(isc::util::OutputBuffer& buf) {
- buf.writeUint16(type_);
- buf.writeUint16(len() - OPTION6_HDR_LEN);
- for (int i = 0; i < values_.size(); ++i) {
- // Depending on the data type length we use different utility functions
- // writeUint16 or writeUint32 which write the data in the network byte
- // order to the provided buffer. The same functions can be safely used
- // for either unsiged or signed integers so there is not need to create
- // special cases for intX_t types.
- switch (OptionDataTypes<T>::len) {
- case 1:
- buf.writeUint8(values_[i]);
- break;
- case 2:
- buf.writeUint16(values_[i]);
- break;
- case 4:
- buf.writeUint32(values_[i]);
- break;
- default:
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- }
- // We don't pack sub-options here because we have array-type option.
- // We don't allow sub-options in array-type options as there is no
- // way to distinguish them from the data fields on option reception.
- }
-
- /// @brief Parses received buffer
- ///
- /// Parses received buffer and returns offset to the first unused byte after
- /// parsed option.
- ///
- /// @param begin iterator to first byte of option data
- /// @param end iterator to end of option data (first byte after option end)
- ///
- /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
- /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
- /// because it is checked in a constructor.
- virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
- if (distance(begin, end) == 0) {
- isc_throw(OutOfRange, "option " << getType() << " empty");
- }
- if (distance(begin, end) % sizeof(T) != 0) {
- isc_throw(OutOfRange, "option " << getType() << " truncated");
- }
- // @todo consider what to do if buffer is longer than data type.
-
- values_.clear();
- while (begin != end) {
- // Depending on the data type length we use different utility functions
- // readUint16 or readUint32 which read the data laid in the network byte
- // order from the provided buffer. The same functions can be safely used
- // for either unsiged or signed integers so there is not need to create
- // special cases for intX_t types.
- int data_size_len = OptionDataTypes<T>::len;
- switch (data_size_len) {
- case 1:
- values_.push_back(*begin);
- break;
- case 2:
- values_.push_back(isc::util::readUint16(&(*begin)));
- break;
- case 4:
- values_.push_back(isc::util::readUint32(&(*begin)));
- break;
- default:
- isc_throw(dhcp::InvalidDataType, "non-integer type");
- }
- // Use local variable to set a new value for this iterator.
- // When using OptionDataTypes<T>::len directly some versions
- // of clang complain about unresolved reference to
- // OptionDataTypes structure during linking.
- begin += data_size_len;
- }
- // We do not unpack sub-options here because we have array-type option.
- // Such option have variable number of data fields, thus there is no
- // way to assess where sub-options start.
- }
-
- /// @brief Return collection of option values.
- ///
- /// @return collection of values.
- const std::vector<T>& getValues() const { return (values_); }
-
- /// @brief Set option values.
- ///
- /// @param values collection of values to be set for option.
- void setValues(const std::vector<T>& values) { values_ = values; }
-
- /// @brief returns complete length of option
- ///
- /// Returns length of this option, including option header and suboptions
- ///
- /// @return length of this option
- virtual uint16_t len() {
- uint16_t length = OPTION6_HDR_LEN + values_.size() * sizeof(T);
- // length of all suboptions
- for (Option::OptionCollection::iterator it = options_.begin();
- it != options_.end();
- ++it) {
- length += (*it).second->len();
- }
- return (length);
- }
-
-private:
-
- std::vector<T> values_;
-};
-
-} // isc::dhcp namespace
-} // isc namespace
-
-#endif // OPTION6_INT_ARRAY_H
diff --git a/src/lib/dhcp/option_custom.cc b/src/lib/dhcp/option_custom.cc
new file mode 100644
index 0000000..068e360
--- /dev/null
+++ b/src/lib/dhcp/option_custom.cc
@@ -0,0 +1,632 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option_data_types.h>
+#include <dhcp/option_custom.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u)
+ : Option(u, def.getCode(), OptionBuffer()),
+ definition_(def) {
+ createBuffers();
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u,
+ const OptionBuffer& data)
+ : Option(u, def.getCode(), data.begin(), data.end()),
+ definition_(def) {
+ // It is possible that no data is provided if an option
+ // is being created on a server side. In such case a bunch
+ // of buffers with default values is first created and then
+ // the values are replaced using writeXXX functions. Thus
+ // we need to detect that no data has been specified and
+ // take a different code path.
+ if (!data_.empty()) {
+ createBuffers(data_);
+ } else {
+ createBuffers();
+ }
+}
+
+OptionCustom::OptionCustom(const OptionDefinition& def,
+ Universe u,
+ OptionBufferConstIter first,
+ OptionBufferConstIter last)
+ : Option(u, def.getCode(), first, last),
+ definition_(def) {
+ createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+ checkArrayType();
+
+ if ((address.isV4() && definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+ (address.isV6() && definition_.getType() != OPT_IPV6_ADDRESS_TYPE)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (definition_.getType() == OPT_IPV4_ADDRESS_TYPE ?
+ "4" : "6") << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::addArrayDataField(const bool value) {
+ checkArrayType();
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeBool(value, buf);
+ buffers_.push_back(buf);
+}
+
+void
+OptionCustom::checkIndex(const uint32_t index) const {
+ if (index >= buffers_.size()) {
+ isc_throw(isc::OutOfRange, "specified data field index " << index
+ << " is out of range.");
+ }
+}
+
+template<typename T>
+void
+OptionCustom::checkDataType(const uint32_t index) const {
+ // Check that the requested return type is a supported integer.
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type"
+ " is not a supported integer type.");
+ }
+
+ // Get the option definition type.
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ definition_.getRecordFields();
+ // When we initialized buffers we have already checked that
+ // the number of these buffers is equal to number of option
+ // fields in the record so the condition below should be met.
+ assert(index < record_fields.size());
+ // Get the data type to be returned.
+ data_type = record_fields[index];
+ }
+
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition for field"
+ " index " << index);
+ }
+}
+
+void
+OptionCustom::createBuffers() {
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+
+ OptionDataType data_type = definition_.getType();
+ // This function is called when an empty data buffer has been
+ // passed to the constructor. In such cases values for particular
+ // data fields will be set using modifier functions but for now
+ // we need to initialize a set of buffers that are specified
+ // for an option by its definition. Since there is no data yet,
+ // we are going to fill these buffers with default values.
+ if (data_type == OPT_RECORD_TYPE) {
+ // For record types we need to iterate over all data fields
+ // specified in option definition and create corresponding
+ // buffers for each of them.
+ const OptionDefinition::RecordFieldsCollection fields =
+ definition_.getRecordFields();
+
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ OptionBuffer buf;
+
+ // For data types that have a fixed size we can use the
+ // utility function to get the buffer's size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable data sizes the utility function returns zero.
+ // It is ok for string values because the default string
+ // is 'empty'. However for FQDN the empty value is not valid
+ // so we initialize it to '.'.
+ if (data_size == 0 &&
+ *field == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // At this point we can resize the buffer. Note that
+ // for string values we are setting the empty buffer
+ // here.
+ buf.resize(data_size);
+ }
+ // We have the buffer with default value prepared so we
+ // add it to the set of buffers.
+ buffers.push_back(buf);
+ }
+ } else if (!definition_.getArrayType() &&
+ data_type != OPT_EMPTY_TYPE) {
+ // For either 'empty' options we don't have to create any buffers
+ // for obvious reason. For arrays we also don't create any buffers
+ // yet because the set of fields that belong to the array is open
+ // ended so we can't allocate required buffers until we know how
+ // many of them are needed.
+ // For non-arrays we have a single value being held by the option
+ // so we have to allocate exactly one buffer.
+ OptionBuffer buf;
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ if (data_size == 0 &&
+ data_type == OPT_FQDN_TYPE) {
+ OptionDataTypeUtil::writeFqdn(".", buf);
+ } else {
+ // Note that if our option holds a string value then
+ // we are making empty buffer here.
+ buf.resize(data_size);
+ }
+ // Add a buffer that we have created and leave.
+ buffers.push_back(buf);
+ }
+ // The 'swap' is used here because we want to make sure that we
+ // don't touch buffers_ until we successfully allocate all
+ // buffers to be stored there.
+ std::swap(buffers, buffers_);
+}
+
+void
+OptionCustom::createBuffers(const OptionBuffer& data_buf) {
+ // Check that the option definition is correct as we are going
+ // to use it to split the data_ buffer into set of sub buffers.
+ definition_.validate();
+
+ std::vector<OptionBuffer> buffers;
+ OptionBuffer::const_iterator data = data_buf.begin();
+
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ // An option comprises a record of data fields. We need to
+ // get types of these data fields to allocate enough space
+ // for each buffer.
+ const OptionDefinition::RecordFieldsCollection& fields =
+ definition_.getRecordFields();
+
+ // Go over all data fields within a record.
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ // For fixed-size data type such as boolean, integer, even
+ // IP address we can use the utility function to get the required
+ // buffer size.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(*field);
+
+ // For variable size types (e.g. string) the function above will
+ // return 0 so we need to do a runtime check of the length.
+ if (data_size == 0) {
+ // FQDN is a special data type as it stores variable length data
+ // but the data length is encoded in the buffer. The easiest way
+ // to obtain the length of the data is to read the FQDN. The
+ // utility function will return the size of the buffer on success.
+ if (*field == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ // In other case we are dealing with string or binary value
+ // which size can't be determined. Thus we consume the
+ // remaining part of the buffer for it. Note that variable
+ // size data can be laid at the end of the option only and
+ // that the validate() function in OptionDefinition object
+ // should have checked wheter it is a case for this option.
+ data_size = std::distance(data, data_buf.end());
+ }
+ if (data_size == 0) {
+ // If we reached the end of buffer we assume that this option is
+ // truncated because there is no remaining data to initialize
+ // an option field.
+ if (data_size == 0) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ }
+ } else {
+ // Our data field requires that there is a certain chunk of
+ // data left in the buffer. If not, option is truncated.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ }
+ // Store the created buffer.
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ // Proceed to the next data field.
+ data += data_size;
+ }
+ } else if (data_type != OPT_EMPTY_TYPE) {
+ // If data_type value is other than OPT_RECORD_TYPE, our option is
+ // empty (have no data at all) or it comprises one or more
+ // data fields of the same type. The type of those fields
+ // is held in the data_type variable so let's use it to determine
+ // a size of buffers.
+ size_t data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ // The check below will fail if the input buffer is too short
+ // for the data size being held by this option.
+ // Note that data_size returned by getDataTypeLen may be zero
+ // if variable length data is being held by the option but
+ // this will not cause this check to throw exception.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ // For an array of values we are taking different path because
+ // we have to handle multiple buffers.
+ if (definition_.getArrayType()) {
+ while (data != data_buf.end()) {
+ // FQDN is a special case because it is of a variable length.
+ // The actual length for a particular FQDN is encoded within
+ // a buffer so we have to actually read the FQDN from a buffer
+ // to get it.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 byte larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ }
+ // We don't perform other checks for data types that can't be
+ // used together with array indicator such as strings, empty field
+ // etc. This is because OptionDefinition::validate function should
+ // have checked this already. Thus data_size must be greater than
+ // zero.
+ assert(data_size > 0);
+ // Get chunks of data and store as a collection of buffers.
+ // Truncate any remaining part which length is not divisible by
+ // data_size. Note that it is ok to truncate the data if and only
+ // if the data buffer is long enough to keep at least one value.
+ // This has been checked above already.
+ if (std::distance(data, data_buf.end()) < data_size) {
+ break;
+ }
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ data += data_size;
+ }
+ } else {
+ // For non-arrays the data_size can be zero because
+ // getDataTypeLen returns zero for variable size data types
+ // such as strings. Simply take whole buffer.
+ if (data_size == 0) {
+ // For FQDN we get the size by actually reading the FQDN.
+ if (data_type == OPT_FQDN_TYPE) {
+ std::string fqdn =
+ OptionDataTypeUtil::readFqdn(OptionBuffer(data, data_buf.end()));
+ // The size of the buffer holding an FQDN is always
+ // 1 bytes larger than the size of the string
+ // representation of this FQDN.
+ data_size = fqdn.size() + 1;
+ } else {
+ data_size = std::distance(data, data_buf.end());
+ }
+ }
+ if (data_size > 0) {
+ buffers.push_back(OptionBuffer(data, data + data_size));
+ } else {
+ isc_throw(OutOfRange, "option buffer truncated");
+ }
+ }
+ }
+ // If everything went ok we can replace old buffer set with new ones.
+ std::swap(buffers_, buffers);
+}
+
+std::string
+OptionCustom::dataFieldToText(const OptionDataType data_type,
+ const uint32_t index) const {
+ std::ostringstream text;
+
+ // Get the value of the data field.
+ switch (data_type) {
+ case OPT_BINARY_TYPE:
+ text << util::encode::encodeHex(readBinary(index));
+ break;
+ case OPT_BOOLEAN_TYPE:
+ text << (readBoolean(index) ? "true" : "false");
+ break;
+ case OPT_INT8_TYPE:
+ text << readInteger<int8_t>(index);
+ break;
+ case OPT_INT16_TYPE:
+ text << readInteger<int16_t>(index);
+ break;
+ case OPT_INT32_TYPE:
+ text << readInteger<int32_t>(index);
+ break;
+ case OPT_UINT8_TYPE:
+ text << readInteger<uint8_t>(index);
+ break;
+ case OPT_UINT16_TYPE:
+ text << readInteger<uint16_t>(index);
+ break;
+ case OPT_UINT32_TYPE:
+ text << readInteger<uint32_t>(index);
+ break;
+ case OPT_IPV4_ADDRESS_TYPE:
+ case OPT_IPV6_ADDRESS_TYPE:
+ text << readAddress(index).toText();
+ break;
+ case OPT_FQDN_TYPE:
+ text << readFqdn(index);
+ break;
+ case OPT_STRING_TYPE:
+ text << readString(index);
+ break;
+ default:
+ ;
+ }
+
+ // Append data field type in brackets.
+ text << " ( " << OptionDataTypeUtil::getDataTypeName(data_type) << " ) ";
+
+ return (text.str());
+}
+
+void
+OptionCustom::pack4(isc::util::OutputBuffer& buf) {
+ if (len() > 255) {
+ isc_throw(OutOfRange, "DHCPv4 Option " << type_
+ << " value is too high. At most 255 is supported.");
+ }
+
+ buf.writeUint8(type_);
+ buf.writeUint8(len() - getHeaderLen());
+
+ // Write data from buffers.
+ for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+ it != buffers_.end(); ++it) {
+ // In theory the createBuffers function should have taken
+ // care that there are no empty buffers added to the
+ // collection but it is almost always good to make sure.
+ if (!it->empty()) {
+ buf.writeData(&(*it)[0], it->size());
+ }
+ }
+
+ // Write suboptions.
+ packOptions(buf);
+}
+
+void
+OptionCustom::pack6(isc::util::OutputBuffer& buf) {
+ buf.writeUint16(type_);
+ buf.writeUint16(len() - getHeaderLen());
+
+ // Write data from buffers.
+ for (std::vector<OptionBuffer>::const_iterator it = buffers_.begin();
+ it != buffers_.end(); ++it) {
+ if (!it->empty()) {
+ buf.writeData(&(*it)[0], it->size());
+ }
+ }
+
+ packOptions(buf);
+}
+
+asiolink::IOAddress
+OptionCustom::readAddress(const uint32_t index) const {
+ checkIndex(index);
+
+ // The address being read can be either IPv4 or IPv6. The decision
+ // is made based on the buffer length. If it holds 4 bytes it is IPv4
+ // address, if it holds 16 bytes it is IPv6.
+ if (buffers_[index].size() == asiolink::V4ADDRESS_LEN) {
+ return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET));
+ } else if (buffers_[index].size() == asiolink::V6ADDRESS_LEN) {
+ return (OptionDataTypeUtil::readAddress(buffers_[index], AF_INET6));
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IP address. Invalid buffer length "
+ << buffers_[index].size() << ".");
+ }
+}
+
+void
+OptionCustom::writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index) {
+ using namespace isc::asiolink;
+
+ checkIndex(index);
+
+ if ((address.isV4() && buffers_[index].size() != V4ADDRESS_LEN) ||
+ (address.isV6() && buffers_[index].size() != V6ADDRESS_LEN)) {
+ isc_throw(BadDataTypeCast, "invalid address specified "
+ << address.toText() << ". Expected a valid IPv"
+ << (buffers_[index].size() == V4ADDRESS_LEN ? "4" : "6")
+ << " address.");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeAddress(address, buf);
+ std::swap(buf, buffers_[index]);
+}
+
+const OptionBuffer&
+OptionCustom::readBinary(const uint32_t index) const {
+ checkIndex(index);
+ return (buffers_[index]);
+}
+
+void
+OptionCustom::writeBinary(const OptionBuffer& buf,
+ const uint32_t index) {
+ checkIndex(index);
+ buffers_[index] = buf;
+}
+
+bool
+OptionCustom::readBoolean(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readBool(buffers_[index]));
+}
+
+void
+OptionCustom::writeBoolean(const bool value, const uint32_t index) {
+ checkIndex(index);
+
+ buffers_[index].clear();
+ OptionDataTypeUtil::writeBool(value, buffers_[index]);
+}
+
+std::string
+OptionCustom::readFqdn(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readFqdn(buffers_[index]));
+}
+
+void
+OptionCustom::writeFqdn(const std::string& fqdn, const uint32_t index) {
+ checkIndex(index);
+
+ // Create a temporay buffer where the FQDN will be written.
+ OptionBuffer buf;
+ // Try to write to the temporary buffer rather than to the
+ // buffers_ member directly guarantees that we don't modify
+ // (clear) buffers_ until we are sure that the provided FQDN
+ // is valid.
+ OptionDataTypeUtil::writeFqdn(fqdn, buf);
+ // If we got to this point it means that the FQDN is valid.
+ // We can move the contents of the teporary buffer to the
+ // target buffer.
+ std::swap(buffers_[index], buf);
+}
+
+std::string
+OptionCustom::readString(const uint32_t index) const {
+ checkIndex(index);
+ return (OptionDataTypeUtil::readString(buffers_[index]));
+}
+
+void
+OptionCustom::writeString(const std::string& text, const uint32_t index) {
+ checkIndex(index);
+
+ // Let's clear a buffer as we want to replace the value of the
+ // whole buffer. If we fail to clear the buffer the data will
+ // be appended.
+ buffers_[index].clear();
+ // If the text value is empty we can leave because the buffer
+ // is already empty.
+ if (!text.empty()) {
+ OptionDataTypeUtil::writeString(text, buffers_[index]);
+ }
+}
+
+void
+OptionCustom::unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ data_ = OptionBuffer(begin, end);
+ // Chop the buffer stored in data_ into set of sub buffers.
+ createBuffers(data_);
+}
+
+uint16_t
+OptionCustom::len() {
+ // The length of the option is a sum of option header ...
+ int length = getHeaderLen();
+
+ // ... lengths of all buffers that hold option data ...
+ for (std::vector<OptionBuffer>::const_iterator buf = buffers_.begin();
+ buf != buffers_.end(); ++buf) {
+ length += buf->size();
+ }
+
+ // ... and lengths of all suboptions
+ for (OptionCustom::OptionCollection::iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+
+ return (length);
+}
+
+void OptionCustom::setData(const OptionBufferConstIter first,
+ const OptionBufferConstIter last) {
+ // We will copy entire option buffer, so we have to resize data_.
+ data_.resize(std::distance(first, last));
+ std::copy(first, last, data_.begin());
+
+ // Chop the data_ buffer into set of buffers that represent
+ // option fields data.
+ createBuffers(data_);
+}
+
+std::string OptionCustom::toText(int indent) {
+ std::stringstream tmp;
+
+ for (int i = 0; i < indent; ++i)
+ tmp << " ";
+
+ tmp << "type=" << type_ << ", len=" << len()-getHeaderLen()
+ << ", data fields:" << std::endl;
+
+ OptionDataType data_type = definition_.getType();
+ if (data_type == OPT_RECORD_TYPE) {
+ const OptionDefinition::RecordFieldsCollection& fields =
+ definition_.getRecordFields();
+
+ // For record types we iterate over fields defined in
+ // option definition and match the appropriate buffer
+ // with them.
+ for (OptionDefinition::RecordFieldsConstIter field = fields.begin();
+ field != fields.end(); ++field) {
+ for (int j = 0; j < indent + 2; ++j) {
+ tmp << " ";
+ }
+ tmp << "#" << std::distance(fields.begin(), field) << " "
+ << dataFieldToText(*field, std::distance(fields.begin(),
+ field))
+ << std::endl;
+ }
+ } else {
+ // For non-record types we iterate over all buffers
+ // and print the data type set globally for an option
+ // definition. We take the same code path for arrays
+ // and non-arrays as they only differ in such a way that
+ // non-arrays have just single data field.
+ for (unsigned int i = 0; i < getDataFieldsNum(); ++i) {
+ for (int j = 0; j < indent + 2; ++j) {
+ tmp << " ";
+ }
+ tmp << "#" << i << " "
+ << dataFieldToText(definition_.getType(), i)
+ << std::endl;
+ }
+ }
+
+ // print suboptions
+ for (OptionCollection::const_iterator opt = options_.begin();
+ opt != options_.end();
+ ++opt) {
+ tmp << (*opt).second->toText(indent+2);
+ }
+ return tmp.str();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
new file mode 100644
index 0000000..0ee4688
--- /dev/null
+++ b/src/lib/dhcp/option_custom.h
@@ -0,0 +1,358 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_CUSTOM_H
+#define OPTION_CUSTOM_H
+
+#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <util/io_utilities.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Option with defined data fields represented as buffers that can
+/// be accessed using data field index.
+///
+/// This class represents an option which has defined structure: data fields
+/// of specific types and order. Those fields can be accessed using indexes,
+/// where index 0 represents first data field within an option. The last
+/// field can be accessed using index equal to 'number of fields' - 1.
+/// Internally, the option data is stored as a collection of OptionBuffer
+/// objects, each representing data for a particular data field. This data
+/// can be converted to the actual data type using methods implemented
+/// within this class. This class is used to represent those options that
+/// can't be represented by any other specialized class (this excludes the
+/// Option class which is generic and can be used to represent any option).
+class OptionCustom : public Option {
+public:
+
+ /// @brief Constructor, used for options to be sent.
+ ///
+ /// This constructor creates an instance of an option with default
+ /// data set for all data fields. The option buffers are allocated
+ /// according to data size being stored in particular data fields.
+ /// For variable size data empty buffers are created.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6)
+ OptionCustom(const OptionDefinition& def, Universe u);
+
+ /// @brief Constructor, used for options to be sent.
+ ///
+ /// This constructor creates an instance of an option from the whole
+ /// supplied buffer. This constructor is mainly used to create an
+ /// instances of options to be stored in outgoing DHCP packets.
+ /// The buffer used to create the instance of an option can be
+ /// created from the option data specified in server's configuration.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6).
+ /// @param data content of the option.
+ ///
+ /// @throw OutOfRange if option buffer is truncated.
+ ///
+ /// @todo list all exceptions thrown by ctor.
+ OptionCustom(const OptionDefinition& def, Universe u, const OptionBuffer& data);
+
+ /// @brief Constructor, used for received options.
+ ///
+ /// This constructor creates an instance an option from the portion
+ /// of the buffer specified by iterators. This is mainly useful when
+ /// parsing received packets. Such packets are represented by a single
+ /// buffer holding option data and all sub options. Methods that are
+ /// parsing a packet, supply relevant portions of the packet buffer
+ /// to this constructor to create option instances out of it.
+ ///
+ /// @param def option definition.
+ /// @param u specifies universe (V4 or V6).
+ /// @param first iterator to the first element that should be copied.
+ /// @param last iterator to the next element after the last one
+ /// to be copied.
+ ///
+ /// @throw OutOfRange if option buffer is truncated.
+ ///
+ /// @todo list all exceptions thrown by ctor.
+ OptionCustom(const OptionDefinition& def, Universe u,
+ OptionBufferConstIter first, OptionBufferConstIter last);
+
+ /// @brief Create new buffer and set its value as an IP address.
+ ///
+ /// @param address IPv4 or IPv6 address to be written to
+ /// a buffer being created.
+ void addArrayDataField(const asiolink::IOAddress& address);
+
+ /// @brief Create new buffer and store boolean value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ void addArrayDataField(const bool value);
+
+ /// @brief Create new buffer and store integer value in it.
+ ///
+ /// @param value value to be stored in the created buffer.
+ /// @tparam T integer type of the value being stored.
+ template<typename T>
+ void addArrayDataField(const T value) {
+ checkArrayType();
+
+ OptionDataType data_type = definition_.getType();
+ if (OptionDataTypeTraits<T>::type != data_type) {
+ isc_throw(isc::dhcp::InvalidDataType,
+ "specified data type " << data_type << " does not"
+ " match the data type in an option definition");
+ }
+
+ OptionBuffer buf;
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ buffers_.push_back(buf);
+ }
+
+ /// @brief Return a number of the data fields.
+ ///
+ /// @return number of data fields held by the option.
+ uint32_t getDataFieldsNum() const { return (buffers_.size()); }
+
+ /// @brief Read a buffer as IP address.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return IP address read from a buffer.
+ /// @throw isc::OutOfRange if index is out of range.
+ asiolink::IOAddress readAddress(const uint32_t index = 0) const;
+
+ /// @brief Write an IP address into a buffer.
+ ///
+ /// @param address IP address being written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if IP address is invalid.
+ void writeAddress(const asiolink::IOAddress& address,
+ const uint32_t index = 0);
+
+ /// @brief Read a buffer as binary data.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return read buffer holding binary data.
+ const OptionBuffer& readBinary(const uint32_t index = 0) const;
+
+ /// @brief Write binary data into a buffer.
+ ///
+ /// @param buf buffer holding binary data to be written.
+ /// @param index buffer index.
+ void writeBinary(const OptionBuffer& buf, const uint32_t index = 0);
+
+ /// @brief Read a buffer as boolean value.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @return read boolean value.
+ bool readBoolean(const uint32_t index = 0) const;
+
+ /// @brief Write a boolean value into a buffer.
+ ///
+ /// @param value boolean value to be written.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeBoolean(const bool value, const uint32_t index = 0);
+
+ /// @brief Read a buffer as FQDN.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if buffer index is out of range.
+ /// @throw isc::dhcp::BadDataTypeCast if a buffer being read
+ /// does not hold a valid FQDN.
+ /// @return string representation if FQDN.
+ std::string readFqdn(const uint32_t index = 0) const;
+
+ /// @brief Write an FQDN into a buffer.
+ ///
+ /// @param fqdn text representation of FQDN.
+ /// @param index buffer index.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void writeFqdn(const std::string& fqdn, const uint32_t index = 0);
+
+ /// @brief Read a buffer as integer value.
+ ///
+ /// @param index buffer index.
+ /// @tparam integer type of a value being returned.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ /// @return read integer value.
+ template<typename T>
+ T readInteger(const uint32_t index = 0) const {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // When we created the buffer we have checked that it has a
+ // valid size so this condition here should be always fulfiled.
+ assert(buffers_[index].size() == OptionDataTypeTraits<T>::len);
+ // Read an integer value.
+ return (OptionDataTypeUtil::readInt<T>(buffers_[index]));
+ }
+
+ /// @brief Write an integer value into a buffer.
+ ///
+ /// @param value integer value to be written.
+ /// @param index buffer index.
+ /// @tparam T integer type of a value being written.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ /// @throw isc::dhcp::InvalidDataType if T is invalid.
+ template<typename T>
+ void writeInteger(const T value, const uint32_t index = 0) {
+ // Check that the index is not out of range.
+ checkIndex(index);
+ // Check that T points to a valid integer type and this type
+ // is consistent with an option definition.
+ checkDataType<T>(index);
+ // Get some temporary buffer.
+ OptionBuffer buf;
+ // Try to write to the buffer.
+ OptionDataTypeUtil::writeInt<T>(value, buf);
+ // If successful, replace the old buffer with new one.
+ std::swap(buffers_[index], buf);
+ }
+
+ /// @brief Read a buffer as string value.
+ ///
+ /// @param index buffer index.
+ ///
+ /// @return string value read from buffer.
+ /// @throw isc::OutOfRange if index is out of range.
+ std::string readString(const uint32_t index = 0) const;
+
+ /// @brief Write a string value into a buffer.
+ ///
+ /// @param text the string value to be written.
+ /// @param index buffer index.
+ void writeString(const std::string& text,
+ const uint32_t index = 0);
+
+ /// @brief Parses received buffer.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ virtual void unpack(OptionBufferConstIter begin,
+ OptionBufferConstIter end);
+
+ /// @brief Returns string representation of the option.
+ ///
+ /// @param indent number of spaces before printed text.
+ ///
+ /// @return string with text representation.
+ virtual std::string toText(int indent = 0);
+
+ /// @brief Returns length of the complete option (data length +
+ /// DHCPv4/DHCPv6 option header)
+ ///
+ /// @return length of the option
+ virtual uint16_t len();
+
+ /// @brief Sets content of this option from buffer.
+ ///
+ /// Option will be resized to length of buffer.
+ ///
+ /// @param first iterator pointing begining of buffer to copy.
+ /// @param last iterator pointing to end of buffer to copy.
+ void setData(const OptionBufferConstIter first,
+ const OptionBufferConstIter last);
+
+protected:
+
+ /// @brief Writes DHCPv4 option in a wire format to a buffer.
+ ///
+ /// @param buf output buffer (option will be stored there).
+ virtual void pack4(isc::util::OutputBuffer& buf);
+
+ /// @brief Writes DHCPv6 option in a wire format to a buffer.
+ ///
+ /// @param buf output buffer (built options will be stored here)
+ virtual void pack6(isc::util::OutputBuffer& buf);
+
+private:
+
+ /// @brief Verify that the option comprises an array of values.
+ ///
+ /// This helper function is used by createArrayEntry functions
+ /// and throws an exception if the particular option is not
+ /// an array.
+ ///
+ /// @throw isc::InvalidOperation if option is not an array.
+ inline void checkArrayType() const {
+ if (!definition_.getArrayType()) {
+ isc_throw(InvalidOperation, "failed to add new array entry to an"
+ << " option. The option is not an array.");
+ }
+ }
+
+ /// @brief Verify that the integer type is consistent with option
+ /// field type.
+ ///
+ /// This convenience function checks that the data type specified as T
+ /// is consistent with a type of a data field identified by index.
+ ///
+ /// @param index data field index.
+ /// @tparam data type to be validated.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if the type is invalid.
+ template<typename T>
+ void checkDataType(const uint32_t index) const;
+
+ /// @brief Check if data field index is valid.
+ ///
+ /// @param index Data field index to check.
+ ///
+ /// @throw isc::OutOfRange if index is out of range.
+ void checkIndex(const uint32_t index) const;
+
+ /// @brief Create a collection of non initialized buffers.
+ void createBuffers();
+
+ /// @brief Create collection of buffers representing data field values.
+ ///
+ /// @param data_buf a buffer to be parsed.
+ void createBuffers(const OptionBuffer& data_buf);
+
+ /// @brief Return a text representation of a data field.
+ ///
+ /// @param data_type data type of a field.
+ /// @param index data field buffer index within a custom option.
+ ///
+ /// @return text representation of a data field.
+ std::string dataFieldToText(const OptionDataType data_type,
+ const uint32_t index) const;
+
+ /// Option definition used to create an option.
+ OptionDefinition definition_;
+
+ /// The collection of buffers holding data for option fields.
+ /// The order of buffers corresponds to the order of option
+ /// fields.
+ std::vector<OptionBuffer> buffers_;
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_CUSTOM_H
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
new file mode 100644
index 0000000..a567b7e
--- /dev/null
+++ b/src/lib/dhcp/option_data_types.cc
@@ -0,0 +1,247 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/option_data_types.h>
+#include <dns/labelsequence.h>
+#include <dns/name.h>
+#include <util/encode/hex.h>
+
+namespace isc {
+namespace dhcp {
+
+OptionDataTypeUtil::OptionDataTypeUtil() {
+ data_types_["empty"] = OPT_EMPTY_TYPE;
+ data_types_["binary"] = OPT_BINARY_TYPE;
+ data_types_["boolean"] = OPT_BOOLEAN_TYPE;
+ data_types_["int8"] = OPT_INT8_TYPE;
+ data_types_["int16"] = OPT_INT16_TYPE;
+ data_types_["int32"] = OPT_INT32_TYPE;
+ data_types_["uint8"] = OPT_UINT8_TYPE;
+ data_types_["uint16"] = OPT_UINT16_TYPE;
+ data_types_["uint32"] = OPT_UINT32_TYPE;
+ data_types_["ipv4-address"] = OPT_IPV4_ADDRESS_TYPE;
+ data_types_["ipv6-address"] = OPT_IPV6_ADDRESS_TYPE;
+ data_types_["string"] = OPT_STRING_TYPE;
+ data_types_["fqdn"] = OPT_FQDN_TYPE;
+ data_types_["record"] = OPT_RECORD_TYPE;
+
+ data_type_names_[OPT_EMPTY_TYPE] = "empty";
+ data_type_names_[OPT_BINARY_TYPE] = "binary";
+ data_type_names_[OPT_BOOLEAN_TYPE] = "boolean";
+ data_type_names_[OPT_INT8_TYPE] = "int8";
+ data_type_names_[OPT_INT16_TYPE] = "int16";
+ data_type_names_[OPT_INT32_TYPE] = "int32";
+ data_type_names_[OPT_UINT8_TYPE] = "uint8";
+ data_type_names_[OPT_UINT16_TYPE] = "uint16";
+ data_type_names_[OPT_UINT32_TYPE] = "uint32";
+ data_type_names_[OPT_IPV4_ADDRESS_TYPE] = "ipv4-address";
+ data_type_names_[OPT_IPV6_ADDRESS_TYPE] = "ipv6-address";
+ data_type_names_[OPT_STRING_TYPE] = "string";
+ data_type_names_[OPT_FQDN_TYPE] = "fqdn";
+ data_type_names_[OPT_RECORD_TYPE] = "record";
+ // The "unknown" data type is declared here so as
+ // it can be returned by reference by a getDataTypeName
+ // function it no other type is suitable. Other than that
+ // this is unused.
+ data_type_names_[OPT_UNKNOWN_TYPE] = "unknown";
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataType(const std::string& data_type) {
+ return (OptionDataTypeUtil::instance().getDataTypeImpl(data_type));
+}
+
+OptionDataType
+OptionDataTypeUtil::getDataTypeImpl(const std::string& data_type) const {
+ std::map<std::string, OptionDataType>::const_iterator data_type_it =
+ data_types_.find(data_type);
+ if (data_type_it != data_types_.end()) {
+ return (data_type_it->second);
+ }
+ return (OPT_UNKNOWN_TYPE);
+}
+
+int
+OptionDataTypeUtil::getDataTypeLen(const OptionDataType data_type) {
+ switch (data_type) {
+ case OPT_BOOLEAN_TYPE:
+ case OPT_INT8_TYPE:
+ case OPT_UINT8_TYPE:
+ return (1);
+
+ case OPT_INT16_TYPE:
+ case OPT_UINT16_TYPE:
+ return (2);
+
+ case OPT_INT32_TYPE:
+ case OPT_UINT32_TYPE:
+ return (4);
+
+ case OPT_IPV4_ADDRESS_TYPE:
+ return (asiolink::V4ADDRESS_LEN);
+
+ case OPT_IPV6_ADDRESS_TYPE:
+ return (asiolink::V6ADDRESS_LEN);
+
+ default:
+ ;
+ }
+ return (0);
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeName(const OptionDataType data_type) {
+ return (OptionDataTypeUtil::instance().getDataTypeNameImpl(data_type));
+}
+
+const std::string&
+OptionDataTypeUtil::getDataTypeNameImpl(const OptionDataType data_type) const {
+ std::map<OptionDataType, std::string>::const_iterator data_type_it =
+ data_type_names_.find(data_type);
+ if (data_type_it != data_type_names_.end()) {
+ return (data_type_it->second);
+ }
+ return (data_type_names_.find(OPT_UNKNOWN_TYPE)->second);
+}
+
+OptionDataTypeUtil&
+OptionDataTypeUtil::instance() {
+ static OptionDataTypeUtil instance;
+ return (instance);
+}
+
+asiolink::IOAddress
+OptionDataTypeUtil::readAddress(const std::vector<uint8_t>& buf,
+ const short family) {
+ using namespace isc::asiolink;
+ if (family == AF_INET) {
+ if (buf.size() < V4ADDRESS_LEN) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IPv4 address. Invalid buffer size: " << buf.size());
+ }
+ return (IOAddress::fromBytes(AF_INET, &buf[0]));
+ } else if (family == AF_INET6) {
+ if (buf.size() < V6ADDRESS_LEN) {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ << " IPv6 address. Invalid buffer size: " << buf.size());
+ }
+ return (IOAddress::fromBytes(AF_INET6, &buf[0]));
+ } else {
+ isc_throw(BadDataTypeCast, "unable to read data from the buffer as"
+ "IP address. Invalid family: " << family);
+ }
+}
+
+void
+OptionDataTypeUtil::writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+}
+
+void
+OptionDataTypeUtil::writeBinary(const std::string& hex_str,
+ std::vector<uint8_t>& buf) {
+ // Binary value means that the value is encoded as a string
+ // of hexadecimal digits. We need to decode this string
+ // to the binary format here.
+ OptionBuffer binary;
+ try {
+ util::encode::decodeHex(hex_str, binary);
+ } catch (const Exception& ex) {
+ isc_throw(BadDataTypeCast, "unable to cast " << hex_str
+ << " to binary data type: " << ex.what());
+ }
+ // Decode was successful so append decoded binary value
+ // to the buffer.
+ buf.insert(buf.end(), binary.begin(), binary.end());
+}
+
+bool
+OptionDataTypeUtil::readBool(const std::vector<uint8_t>& buf) {
+ if (buf.size() < 1) {
+ isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+ << " value. Invalid buffer size " << buf.size());
+ }
+ if (buf[0] == 1) {
+ return (true);
+ } else if (buf[0] == 0) {
+ return (false);
+ }
+ isc_throw(BadDataTypeCast, "unable to read the buffer as boolean"
+ << " value. Invalid value " << static_cast<int>(buf[0]));
+}
+
+void
+OptionDataTypeUtil::writeBool(const bool value,
+ std::vector<uint8_t>& buf) {
+ buf.push_back(static_cast<uint8_t>(value ? 1 : 0));
+}
+
+std::string
+OptionDataTypeUtil::readFqdn(const std::vector<uint8_t>& buf) {
+ // If buffer is empty emit an error.
+ if (buf.empty()) {
+ isc_throw(BadDataTypeCast, "unable to read FQDN from a buffer."
+ << " The buffer is empty.");
+ }
+ // Set up an InputBuffer so as we can use isc::dns::Name object to get the FQDN.
+ isc::util::InputBuffer in_buf(static_cast<const void*>(&buf[0]), buf.size());
+ try {
+ // Try to create an object from the buffer. If exception is thrown
+ // it means that the buffer doesn't hold a valid domain name (invalid
+ // syntax).
+ isc::dns::Name name(in_buf);
+ return (name.toText());
+ } catch (const isc::Exception& ex) {
+ // Unable to convert the data in the buffer into FQDN.
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+void
+OptionDataTypeUtil::writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf) {
+ try {
+ isc::dns::Name name(fqdn);
+ isc::dns::LabelSequence labels(name);
+ if (labels.getDataLength() > 0) {
+ size_t read_len = 0;
+ const uint8_t* data = labels.getData(&read_len);
+ buf.insert(buf.end(), data, data + read_len);
+ }
+ } catch (const isc::Exception& ex) {
+ isc_throw(BadDataTypeCast, ex.what());
+ }
+}
+
+std::string
+OptionDataTypeUtil::readString(const std::vector<uint8_t>& buf) {
+ std::string value;
+ if (!buf.empty()) {
+ value.insert(value.end(), buf.begin(), buf.end());
+ }
+ return (value);
+}
+
+void
+OptionDataTypeUtil::writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ if (value.size() > 0) {
+ buf.insert(buf.end(), value.begin(), value.end());
+ }
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index 0ad14d2..e53fa6e 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -15,7 +15,10 @@
#ifndef OPTION_DATA_TYPES_H
#define OPTION_DATA_TYPES_H
+#include <asiolink/io_address.h>
+#include <dhcp/option.h>
#include <exceptions/exceptions.h>
+#include <util/io_utilities.h>
#include <stdint.h>
@@ -29,57 +32,396 @@ public:
isc::Exception(file, line, what) { };
};
-/// @brief Trait class for integer data types supported in DHCP option definitions.
+/// @brief Exception to be thrown when cast to the data type was unsuccessful.
+class BadDataTypeCast : public Exception {
+public:
+ BadDataTypeCast(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+
+/// @brief Data types of DHCP option fields.
+///
+/// @warning The order of data types matters: OPT_UNKNOWN_TYPE
+/// must always be the last position. Also, OPT_RECORD_TYPE
+/// must be at last but one position. This is because some
+/// functions perform sanity checks on data type values using
+/// '>' operators, assuming that all values beyond the
+/// OPT_RECORD_TYPE are invalid.
+enum OptionDataType {
+ OPT_EMPTY_TYPE,
+ OPT_BINARY_TYPE,
+ OPT_BOOLEAN_TYPE,
+ OPT_INT8_TYPE,
+ OPT_INT16_TYPE,
+ OPT_INT32_TYPE,
+ OPT_UINT8_TYPE,
+ OPT_UINT16_TYPE,
+ OPT_UINT32_TYPE,
+ OPT_ANY_ADDRESS_TYPE,
+ OPT_IPV4_ADDRESS_TYPE,
+ OPT_IPV6_ADDRESS_TYPE,
+ OPT_STRING_TYPE,
+ OPT_FQDN_TYPE,
+ OPT_RECORD_TYPE,
+ OPT_UNKNOWN_TYPE
+};
+
+/// @brief Trait class for data types supported in DHCP option definitions.
///
/// This is useful to check whether the type specified as template parameter
-/// is supported by classes like Option6Int, Option6IntArray and some template
+/// is supported by classes like OptionInt, OptionIntArray and some template
/// factory functions in OptionDefinition class.
template<typename T>
-struct OptionDataTypes {
+struct OptionDataTypeTraits {
static const bool valid = false;
static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_UNKNOWN_TYPE;
+};
+
+/// binary type is supported
+template<>
+struct OptionDataTypeTraits<OptionBuffer> {
+ static const bool valid = true;
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_BINARY_TYPE;
+};
+
+/// bool type is supported
+template<>
+struct OptionDataTypeTraits<bool> {
+ static const bool valid = true;
+ static const int len = sizeof(bool);
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_BOOLEAN_TYPE;
};
/// int8_t type is supported.
template<>
-struct OptionDataTypes<int8_t> {
+struct OptionDataTypeTraits<int8_t> {
static const bool valid = true;
static const int len = 1;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT8_TYPE;
};
/// int16_t type is supported.
template<>
-struct OptionDataTypes<int16_t> {
+struct OptionDataTypeTraits<int16_t> {
static const bool valid = true;
static const int len = 2;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT16_TYPE;
};
/// int32_t type is supported.
template<>
-struct OptionDataTypes<int32_t> {
+struct OptionDataTypeTraits<int32_t> {
static const bool valid = true;
static const int len = 4;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_INT32_TYPE;
};
/// uint8_t type is supported.
template<>
-struct OptionDataTypes<uint8_t> {
+struct OptionDataTypeTraits<uint8_t> {
static const bool valid = true;
static const int len = 1;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT8_TYPE;
};
/// uint16_t type is supported.
template<>
-struct OptionDataTypes<uint16_t> {
+struct OptionDataTypeTraits<uint16_t> {
static const bool valid = true;
static const int len = 2;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT16_TYPE;
};
/// uint32_t type is supported.
template<>
-struct OptionDataTypes<uint32_t> {
+struct OptionDataTypeTraits<uint32_t> {
static const bool valid = true;
static const int len = 4;
+ static const bool integer_type = true;
+ static const OptionDataType type = OPT_UINT32_TYPE;
+};
+
+/// IPv4 and IPv6 address type is supported
+template<>
+struct OptionDataTypeTraits<asiolink::IOAddress> {
+ static const bool valid = true;
+ // The len value is used to determine the size of the data
+ // to be written to an option buffer. IOAddress object may
+ // either represent an IPv4 or IPv6 addresses which have
+ // different lengths. Thus we can't put fixed value here.
+ // The length of a data to be written into an option buffer
+ // have to be determined in the runtime for a particular
+ // IOAddress object. Thus setting len to zero.
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_ANY_ADDRESS_TYPE;
+};
+
+/// string type is supported
+template<>
+struct OptionDataTypeTraits<std::string> {
+ static const bool valid = true;
+ // The len value is used to determine the size of the data
+ // to be written to an option buffer. For strings this
+ // size is unknown until we actually deal with the particular
+ // string to be written. Thus setting it to zero.
+ static const int len = 0;
+ static const bool integer_type = false;
+ static const OptionDataType type = OPT_STRING_TYPE;
+};
+
+/// @brief Utility class for option data types.
+///
+/// This class provides a set of utility functions to operate on
+/// supported DHCP option data types. It includes conversion
+/// between enumerator values representing data types and data
+/// type names. It also includes a set of functions that write
+/// data into option buffers and read data from option buffers.
+/// The data being written and read are converted from/to actual
+/// data types.
+/// @note This is a singleton class but it can be accessed via
+/// static methods only.
+class OptionDataTypeUtil {
+public:
+
+ /// @brief Return option data type from its name.
+ ///
+ /// @param data_type data type name.
+ /// @return option data type.
+ static OptionDataType getDataType(const std::string& data_type);
+
+ /// @brief Return option data type name from the data type enumerator.
+ ///
+ /// @param data_type option data type.
+ /// @return option data type name.
+ static const std::string& getDataTypeName(const OptionDataType data_type);
+
+ /// @brief Get data type buffer length.
+ ///
+ /// This function returns the size of a particular data type.
+ /// Values retured by this function correspond to the data type
+ /// sizes defined in OptionDataTypeTraits (IPV4_ADDRESS_TYPE and
+ /// IPV6_ADDRESS_TYPE are exceptions here) so they rather indicate
+ /// the fixed length of the data being written into the buffer,
+ /// not the size of the particular data type. Thus for data types
+ /// such as string, binary etc. for which the buffer length can't
+ /// be determined this function returns 0.
+ /// In addition, this function returns the data sizes for
+ /// IPV4_ADDRESS_TYPE and IPV6_ADDRESS_TYPE as their buffer
+ /// representations have fixed data lengths: 4 and 16 respectively.
+ ///
+ /// @param data_type data type which size is to be returned.
+ /// @return data type size or zero for variable length types.
+ static int getDataTypeLen(const OptionDataType data_type);
+
+ /// @brief Read IPv4 or IPv6 address from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @param family address family: AF_INET or AF_INET6.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated.
+ /// @return address being read.
+ static asiolink::IOAddress readAddress(const std::vector<uint8_t>& buf,
+ const short family);
+
+ /// @brief Append IPv4 or IPv6 address to a buffer.
+ ///
+ /// @param address IPv4 or IPv6 address.
+ /// @param [out] buf output buffer.
+ static void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Append hex-encoded binary values to a buffer.
+ ///
+ /// @param hex_str string representing a binary value encoded
+ /// with hexadecimal digits (without 0x prefix).
+ /// @param [out] buf output buffer.
+ static void writeBinary(const std::string& hex_str,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read boolean value from a buffer.
+ ///
+ /// @param buf input buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data being read
+ /// is truncated or the value is invalid (neither 1 nor 0).
+ /// @return boolean value read from a buffer.
+ static bool readBool(const std::vector<uint8_t>& buf);
+
+ /// @brief Append boolean value into a buffer.
+ ///
+ /// The bool value is encoded in a buffer in such a way that
+ /// "1" means "true" and "0" means "false".
+ ///
+ /// @param value boolean value to be written.
+ /// @param [out] buf output buffer.
+ static void writeBool(const bool value, std::vector<uint8_t>& buf);
+
+ /// @brief Read integer value from a buffer.
+ ///
+ /// @param buf input buffer.
+ /// @tparam integer type of the returned value.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast when the data in the buffer
+ /// is truncated.
+ /// @return integer value being read.
+ template<typename T>
+ static T readInt(const std::vector<uint8_t>& buf) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
+ " by readInteger is unsupported integer type");
+ }
+
+ if (buf.size() < OptionDataTypeTraits<T>::len) {
+ isc_throw(isc::dhcp::BadDataTypeCast,
+ "failed to read an integer value from a buffer"
+ << " - buffer is truncated.");
+ }
+
+ T value;
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ value = *(buf.begin());
+ break;
+ case 2:
+ // Calling readUint16 works either for unsigned
+ // or signed types.
+ value = isc::util::readUint16(&(*buf.begin()));
+ break;
+ case 4:
+ // Calling readUint32 works either for unsigned
+ // or signed types.
+ value = isc::util::readUint32(&(*buf.begin()));
+ break;
+ default:
+ // This should not happen because we made checks on data types
+ // but it does not hurt to keep throw statement here.
+ isc_throw(isc::dhcp::InvalidDataType,
+ "invalid size of the data type to be read as integer.");
+ }
+ return (value);
+ }
+
+ /// @brief Append integer or unsigned integer value to a buffer.
+ ///
+ /// @param value an integer value to be written into a buffer.
+ /// @param [out] buf output buffer.
+ /// @tparam data type of the value.
+ template<typename T>
+ static void writeInt(const T value,
+ std::vector<uint8_t>& buf) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(InvalidDataType, "provided data type is not the supported.");
+ }
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.push_back(static_cast<uint8_t>(value));
+ break;
+ case 2:
+ buf.resize(buf.size() + 2);
+ isc::util::writeUint16(static_cast<uint16_t>(value), &buf[buf.size() - 2]);
+ break;
+ case 4:
+ buf.resize(buf.size() + 4);
+ isc::util::writeUint32(static_cast<uint32_t>(value), &buf[buf.size() - 4]);
+ break;
+ default:
+ // The cases above cover whole range of possible data lengths because
+ // we check at the beginning of this function that given data type is
+ // a supported integer type which can be only 1,2 or 4 bytes long.
+ ;
+ }
+ }
+
+ /// @brief Read FQDN from a buffer as a string value.
+ ///
+ /// The format of an FQDN within a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param buf input buffer holding a FQDN.
+ ///
+ /// @throw BadDataTypeCast if a FQDN stored within a buffer is
+ /// invalid (e.g. empty, contains invalid characters, truncated).
+ /// @return fully qualified domain name in a text form.
+ static std::string readFqdn(const std::vector<uint8_t>& buf);
+
+ /// @brief Append FQDN into a buffer.
+ ///
+ /// This method appends the Fully Qualified Domain Name (FQDN)
+ /// represented as string value into a buffer. The format of
+ /// the FQDN being stored into a buffer complies with RFC1035,
+ /// section 3.1.
+ ///
+ /// @param fqdn fully qualified domain name to be written.
+ /// @param [out] buf output buffer.
+ ///
+ /// @throw isc::dhcp::BadDataTypeCast if provided FQDN
+ /// is invalid.
+ static void writeFqdn(const std::string& fqdn,
+ std::vector<uint8_t>& buf);
+
+ /// @brief Read string value from a buffer.
+ ///
+ /// @param buf input buffer.
+ ///
+ /// @return string value being read.
+ static std::string readString(const std::vector<uint8_t>& buf);
+
+ /// @brief Write UTF8-encoded string into a buffer.
+ ///
+ /// @param value string value to be written into a buffer.
+ /// @param [out] buf output buffer.
+ static void writeString(const std::string& value,
+ std::vector<uint8_t>& buf);
+private:
+
+ /// The container holding mapping of data type names to
+ /// data types enumerator.
+ std::map<std::string, OptionDataType> data_types_;
+
+ /// The container holding mapping of data types to data
+ /// type names.
+ std::map<OptionDataType, std::string> data_type_names_;
+
+ /// @brief Private constructor.
+ ///
+ /// This constructor is private because this class should
+ /// be used as singleton (through static public functions).
+ OptionDataTypeUtil();
+
+ /// @brief Return instance of OptionDataTypeUtil
+ ///
+ /// This function is used by some of the public static functions
+ /// to create an instance of OptionDataTypeUtil class.
+ /// When instance is called it calls the class'es constructor
+ /// and initializes some of the private data members.
+ ///
+ /// @return instance of OptionDataTypeUtil singleton.
+ static OptionDataTypeUtil& instance();
+
+ /// @brief Return option data type from its name.
+ ///
+ /// @param data_type data type name.
+ /// @return option data type.
+ OptionDataType getDataTypeImpl(const std::string& data_type) const;
+
+ /// @brief Return option data type name from the data type enumerator.
+ ///
+ /// @param data_type option data type.
+ /// @return option data type name.
+ const std::string& getDataTypeNameImpl(const OptionDataType data_type) const;
};
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index ea3ced6..d2b5aae 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -17,9 +17,14 @@
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int.h>
-#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
+#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace isc::util;
@@ -27,32 +32,6 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
-OptionDefinition::DataTypeUtil::DataTypeUtil() {
- data_types_["empty"] = EMPTY_TYPE;
- data_types_["binary"] = BINARY_TYPE;
- data_types_["boolean"] = BOOLEAN_TYPE;
- data_types_["int8"] = INT8_TYPE;
- data_types_["int16"] = INT16_TYPE;
- data_types_["int32"] = INT32_TYPE;
- data_types_["uint8"] = UINT8_TYPE;
- data_types_["uint16"] = UINT16_TYPE;
- data_types_["uint32"] = UINT32_TYPE;
- data_types_["ipv4-address"] = IPV4_ADDRESS_TYPE;
- data_types_["ipv6-address"] = IPV6_ADDRESS_TYPE;
- data_types_["string"] = STRING_TYPE;
- data_types_["fqdn"] = FQDN_TYPE;
- data_types_["record"] = RECORD_TYPE;
-}
-
-OptionDefinition::DataType
-OptionDefinition::DataTypeUtil::getDataType(const std::string& data_type) {
- std::map<std::string, DataType>::const_iterator data_type_it =
- data_types_.find(data_type);
- if (data_type_it != data_types_.end()) {
- return (data_type_it->second);
- }
- return UNKNOWN_TYPE;
-}
OptionDefinition::OptionDefinition(const std::string& name,
const uint16_t code,
@@ -60,17 +39,17 @@ OptionDefinition::OptionDefinition(const std::string& name,
const bool array_type /* = false */)
: name_(name),
code_(code),
- type_(UNKNOWN_TYPE),
+ type_(OPT_UNKNOWN_TYPE),
array_type_(array_type) {
// Data type is held as enum value by this class.
// Use the provided option type string to get the
// corresponding enum value.
- type_ = DataTypeUtil::instance().getDataType(type);
+ type_ = OptionDataTypeUtil::getDataType(type);
}
OptionDefinition::OptionDefinition(const std::string& name,
const uint16_t code,
- const DataType type,
+ const OptionDataType type,
const bool array_type /* = false */)
: name_(name),
code_(code),
@@ -80,67 +59,144 @@ OptionDefinition::OptionDefinition(const std::string& name,
void
OptionDefinition::addRecordField(const std::string& data_type_name) {
- DataType data_type = DataTypeUtil::instance().getDataType(data_type_name);
+ OptionDataType data_type = OptionDataTypeUtil::getDataType(data_type_name);
addRecordField(data_type);
}
void
-OptionDefinition::addRecordField(const DataType data_type) {
- if (type_ != RECORD_TYPE) {
+OptionDefinition::addRecordField(const OptionDataType data_type) {
+ if (type_ != OPT_RECORD_TYPE) {
isc_throw(isc::InvalidOperation, "'record' option type must be used"
" to add data fields to the record");
}
- if (data_type >= UNKNOWN_TYPE) {
- isc_throw(isc::BadValue, "attempted to add invalid data type to the record");
+ if (data_type >= OPT_RECORD_TYPE ||
+ data_type == OPT_ANY_ADDRESS_TYPE ||
+ data_type == OPT_EMPTY_TYPE) {
+ isc_throw(isc::BadValue, "attempted to add invalid data type to the record.");
}
record_fields_.push_back(data_type);
}
-Option::Factory*
-OptionDefinition::getFactory() const {
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const {
validate();
- // @todo This function must be extended to return more factory
- // functions that create instances of more specialized options.
- // This requires us to first implement those more specialized
- // options that will be derived from Option class.
- if (type_ == BINARY_TYPE) {
- return (factoryGeneric);
- } else if (type_ == IPV6_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList6);
- } else if (type_ == IPV4_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList4);
- } else if (type_ == EMPTY_TYPE) {
- return (factoryEmpty);
- } else if (code_ == D6O_IA_NA && haveIA6Format()) {
- return (factoryIA6);
- } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
- return (factoryIAAddr6);
- } else if (type_ == UINT8_TYPE) {
- if (array_type_) {
- return (factoryGeneric);
- } else {
- return (factoryInteger<uint8_t>);
+ try {
+ switch(type_) {
+ case OPT_EMPTY_TYPE:
+ return (factoryEmpty(u, type));
+
+ case OPT_BINARY_TYPE:
+ return (factoryGeneric(u, type, begin, end));
+
+ case OPT_UINT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<uint8_t>(u, type, begin, end));
+
+ case OPT_INT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<int8_t>(u, type, begin, end));
+
+ case OPT_UINT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ factoryInteger<uint16_t>(u, type, begin, end));
+
+ case OPT_INT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(u, type, begin, end) :
+ factoryInteger<int16_t>(u, type, begin, end));
+
+ case OPT_UINT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ factoryInteger<uint32_t>(u, type, begin, end));
+
+ case OPT_INT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(u, type, begin, end) :
+ factoryInteger<int32_t>(u, type, begin, end));
+
+ case OPT_IPV4_ADDRESS_TYPE:
+ // If definition specifies that an option is an array
+ // of IPv4 addresses we return an instance of specialized
+ // class (OptionAddrLst4). For non-array types there is no
+ // specialized class yet implemented so we drop through
+ // to return an instance of OptionCustom.
+ if (array_type_) {
+ return (factoryAddrList4(type, begin, end));
+ }
+ break;
+
+ case OPT_IPV6_ADDRESS_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
+ if (array_type_) {
+ return (factoryAddrList6(type, begin, end));
+ }
+ break;
+
+ default:
+ if (u == Option::V6) {
+ if ((code_ == D6O_IA_NA || code_ == D6O_IA_PD) &&
+ haveIA6Format()) {
+ // Return Option6IA instance for IA_PD and IA_NA option
+ // types only. We don't want to return Option6IA for other
+ // options that comprise 3 UINT32 data fields because
+ // Option6IA accessors' and modifiers' names are derived
+ // from the IA_NA and IA_PD options' field names: IAID,
+ // T1, T2. Using functions such as getIAID, getT1 etc. for
+ // options other than IA_NA and IA_PD would be bad practice
+ // and cause confusion.
+ return (factoryIA6(type, begin, end));
+
+ } else if (code_ == D6O_IAADDR && haveIAAddr6Format()) {
+ // Rerurn Option6IAAddr option instance for the IAADDR
+ // option only for the same reasons as described in
+ // for IA_NA and IA_PD above.
+ return (factoryIAAddr6(type, begin, end));
+ }
+ }
}
- } else if (type_ == UINT16_TYPE) {
- if (array_type_) {
- return (factoryIntegerArray<uint16_t>);
- } else {
- return (factoryInteger<uint16_t>);
+ return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
+
+ } catch (const Exception& ex) {
+ isc_throw(InvalidOptionValue, ex.what());
+ }
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf) const {
+ return (optionFactory(u, type, buf.begin(), buf.end()));
+}
+
+OptionPtr
+OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
+ const std::vector<std::string>& values) const {
+ validate();
+
+ OptionBuffer buf;
+ if (!array_type_ && type_ != OPT_RECORD_TYPE) {
+ if (values.empty()) {
+ isc_throw(InvalidOptionValue, "no option value specified");
}
- } else if (type_ == UINT32_TYPE) {
- if (array_type_) {
- return (factoryIntegerArray<uint32_t>);
- } else {
- return (factoryInteger<uint32_t>);
+ writeToBuffer(util::str::trim(values[0]), type_, buf);
+ } else if (array_type_ && type_ != OPT_RECORD_TYPE) {
+ for (size_t i = 0; i < values.size(); ++i) {
+ writeToBuffer(util::str::trim(values[i]), type_, buf);
+ }
+ } else if (type_ == OPT_RECORD_TYPE) {
+ const RecordFieldsCollection& records = getRecordFields();
+ if (records.size() > values.size()) {
+ isc_throw(InvalidOptionValue, "number of data fields for the option"
+ << " type " << type_ << " is greater than number of values"
+ << " provided.");
+ }
+ for (size_t i = 0; i < records.size(); ++i) {
+ writeToBuffer(util::str::trim(values[i]),
+ records[i], buf);
}
}
- // Factory generic returns instance of Option class. However, once we
- // implement CustomOption class we may want to return factory function
- // that will create instance of CustomOption rather than Option.
- // CustomOption will allow to access particular data fields within the
- // option rather than raw data buffer.
- return (factoryGeneric);
+ return (optionFactory(u, type, buf.begin(), buf.end()));
}
void
@@ -153,28 +209,95 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
void
OptionDefinition::validate() const {
- // Option name must not be empty.
- if (name_.empty()) {
- isc_throw(isc::BadValue, "option name must not be empty");
- }
- // Option name must not contain spaces.
- if (name_.find(" ") != string::npos) {
- isc_throw(isc::BadValue, "option name must not contain spaces");
+
+ using namespace boost::algorithm;
+
+ std::ostringstream err_str;
+
+ // Allowed characters in the option name are: lower or
+ // upper case letters, digits, underscores and hyphens.
+ // Empty option spaces are not allowed.
+ if (!all(name_, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) ||
+ name_.empty() ||
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option name.
+ all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+ all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+ err_str << "invalid option name '" << name_ << "'";
+
+ } else if (type_ >= OPT_UNKNOWN_TYPE) {
+ // Option definition must be of a known type.
+ err_str << "option type value " << type_ << " is out of range.";
+
+ } else if (array_type_) {
+ if (type_ == OPT_STRING_TYPE) {
+ // Array of strings is not allowed because there is no way
+ // to determine the size of a particular string and thus there
+ // it no way to tell when other data fields begin.
+ err_str << "array of strings is not a valid option definition.";
+ } else if (type_ == OPT_BINARY_TYPE) {
+ err_str << "array of binary values is not a valid option definition.";
+
+ } else if (type_ == OPT_EMPTY_TYPE) {
+ err_str << "array of empty value is not a valid option definition.";
+
+ }
+
+ } else if (type_ == OPT_RECORD_TYPE) {
+ // At least two data fields should be added to the record. Otherwise
+ // non-record option definition could be used.
+ if (getRecordFields().size() < 2) {
+ err_str << "invalid number of data fields: " << getRecordFields().size()
+ << " specified for the option of type 'record'. Expected at"
+ << " least 2 fields.";
+
+ } else {
+ // If the number of fields is valid we have to check if their order
+ // is valid too. We check that string or binary data fields are not
+ // laid before other fields. But we allow that they are laid at the end of
+ // an option.
+ const RecordFieldsCollection& fields = getRecordFields();
+ for (RecordFieldsConstIter it = fields.begin();
+ it != fields.end(); ++it) {
+ if (*it == OPT_STRING_TYPE &&
+ it < fields.end() - 1) {
+ err_str << "string data field can't be laid before data fields"
+ << " of other types.";
+ break;
+ }
+ if (*it == OPT_BINARY_TYPE &&
+ it < fields.end() - 1) {
+ err_str << "binary data field can't be laid before data fields"
+ << " of other types.";
+ }
+ /// Empty type is not allowed within a record.
+ if (*it == OPT_EMPTY_TYPE) {
+ err_str << "empty data type can't be stored as a field in an"
+ << " option record.";
+ break;
+ }
+ }
+ }
+
}
- // Unsupported option types are not allowed.
- if (type_ >= UNKNOWN_TYPE) {
- isc_throw(isc::OutOfRange, "option type value " << type_
- << " is out of range");
+
+ // Non-empty error string means that we have hit the error. We throw
+ // exception and include error string.
+ if (!err_str.str().empty()) {
+ isc_throw(MalformedOptionDefinition, err_str.str());
}
}
bool
-OptionDefinition::haveIAx6Format(OptionDefinition::DataType first_type) const {
- return (haveType(RECORD_TYPE) &&
+OptionDefinition::haveIAx6Format(OptionDataType first_type) const {
+ return (haveType(OPT_RECORD_TYPE) &&
record_fields_.size() == 3 &&
record_fields_[0] == first_type &&
- record_fields_[1] == UINT32_TYPE &&
- record_fields_[2] == UINT32_TYPE);
+ record_fields_[1] == OPT_UINT32_TYPE &&
+ record_fields_[2] == OPT_UINT32_TYPE);
}
bool
@@ -185,70 +308,181 @@ OptionDefinition::haveIA6Format() const {
// arrays do not impose limitations on number of elements in
// the array while this limitation is needed for IA_NA - need
// exactly 3 elements.
- return (haveIAx6Format(UINT32_TYPE));
+ return (haveIAx6Format(OPT_UINT32_TYPE));
}
bool
OptionDefinition::haveIAAddr6Format() const {
- return (haveIAx6Format(IPV6_ADDRESS_TYPE));
+ return (haveIAx6Format(OPT_IPV6_ADDRESS_TYPE));
+}
+
+template<typename T>
+T OptionDefinition::lexicalCastWithRangeCheck(const std::string& value_str) const {
+ // Lexical cast in case of our data types make sense only
+ // for uintX_t, intX_t and bool type.
+ if (!OptionDataTypeTraits<T>::integer_type &&
+ OptionDataTypeTraits<T>::type != OPT_BOOLEAN_TYPE) {
+ isc_throw(BadDataTypeCast, "unable to do lexical cast to non-integer and"
+ << " non-boolean data type");
+ }
+ // We use the 64-bit value here because it has wider range than
+ // any other type we use here and it allows to detect out of
+ // bounds conditions e.g. negative value specified for uintX_t
+ // data type. Obviously if the value exceeds the limits of int64
+ // this function will not handle that properly.
+ int64_t result = 0;
+ try {
+ result = boost::lexical_cast<int64_t>(value_str);
+ } catch (const boost::bad_lexical_cast& ex) {
+ // Prepare error message here.
+ std::string data_type_str = "boolean";
+ if (OptionDataTypeTraits<T>::integer_type) {
+ data_type_str = "integer";
+ }
+ isc_throw(BadDataTypeCast, "unable to do lexical cast to " << data_type_str
+ << " data type for value " << value_str << ": " << ex.what());
+ }
+ // Perform range checks for integer values only (exclude bool values).
+ if (OptionDataTypeTraits<T>::integer_type) {
+ if (result > numeric_limits<T>::max() ||
+ result < numeric_limits<T>::min()) {
+ isc_throw(BadDataTypeCast, "unable to do lexical cast for value "
+ << value_str << ". This value is expected to be in the range of "
+ << numeric_limits<T>::min() << ".." << numeric_limits<T>::max());
+ }
+ }
+ return (static_cast<T>(result));
+}
+
+void
+OptionDefinition::writeToBuffer(const std::string& value,
+ const OptionDataType type,
+ OptionBuffer& buf) const {
+ // We are going to write value given by value argument to the buffer.
+ // The actual type of the value is given by second argument. Check
+ // this argument to determine how to write this value to the buffer.
+ switch (type) {
+ case OPT_BINARY_TYPE:
+ OptionDataTypeUtil::writeBinary(value, buf);
+ return;
+ case OPT_BOOLEAN_TYPE:
+ // We encode the true value as 1 and false as 0 on 8 bits.
+ // That way we actually waste 7 bits but it seems to be the
+ // simpler way to encode boolean.
+ // @todo Consider if any other encode methods can be used.
+ OptionDataTypeUtil::writeBool(lexicalCastWithRangeCheck<bool>(value), buf);
+ return;
+ case OPT_INT8_TYPE:
+ OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<int8_t>(value),
+ buf);
+ return;
+ case OPT_INT16_TYPE:
+ OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<int16_t>(value),
+ buf);
+ return;
+ case OPT_INT32_TYPE:
+ OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<int32_t>(value),
+ buf);
+ return;
+ case OPT_UINT8_TYPE:
+ OptionDataTypeUtil::writeInt<uint8_t>(lexicalCastWithRangeCheck<uint8_t>(value),
+ buf);
+ return;
+ case OPT_UINT16_TYPE:
+ OptionDataTypeUtil::writeInt<uint16_t>(lexicalCastWithRangeCheck<uint16_t>(value),
+ buf);
+ return;
+ case OPT_UINT32_TYPE:
+ OptionDataTypeUtil::writeInt<uint32_t>(lexicalCastWithRangeCheck<uint32_t>(value),
+ buf);
+ return;
+ case OPT_IPV4_ADDRESS_TYPE:
+ case OPT_IPV6_ADDRESS_TYPE:
+ {
+ asiolink::IOAddress address(value);
+ if (!address.isV4() && !address.isV6()) {
+ isc_throw(BadDataTypeCast, "provided address " << address.toText()
+ << " is not a valid IPv4 or IPv6 address.");
+ }
+ OptionDataTypeUtil::writeAddress(address, buf);
+ return;
+ }
+ case OPT_STRING_TYPE:
+ OptionDataTypeUtil::writeString(value, buf);
+ return;
+ case OPT_FQDN_TYPE:
+ {
+ // FQDN implementation is not terribly complicated but will require
+ // creation of some additional logic (maybe object) that will parse
+ // the fqdn into labels.
+ isc_throw(isc::NotImplemented, "write of FQDN record into option buffer"
+ " is not supported yet");
+ return;
+ }
+ default:
+ // We hit this point because invalid option data type has been specified
+ // This may be the case because 'empty' or 'record' data type has been
+ // specified. We don't throw exception here because it will be thrown
+ // at the exit point from this function.
+ ;
+ }
+ isc_throw(isc::BadValue, "attempt to write invalid option data field type"
+ " into the option buffer: " << type);
+
}
OptionPtr
-OptionDefinition::factoryAddrList4(Option::Universe u, uint16_t type,
- const OptionBuffer& buf) {
- sanityCheckUniverse(u, Option::V4);
- boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, buf.begin(),
- buf.begin() + buf.size()));
+OptionDefinition::factoryAddrList4(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<Option4AddrLst> option(new Option4AddrLst(type, begin, end));
return (option);
}
OptionPtr
-OptionDefinition::factoryAddrList6(Option::Universe u, uint16_t type,
- const OptionBuffer& buf) {
- sanityCheckUniverse(u, Option::V6);
- boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, buf.begin(),
- buf.begin() + buf.size()));
+OptionDefinition::factoryAddrList6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ boost::shared_ptr<Option6AddrLst> option(new Option6AddrLst(type, begin, end));
return (option);
}
OptionPtr
-OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
- if (buf.size() > 0) {
- isc_throw(isc::BadValue, "input option buffer must be empty"
- " when creating empty option instance");
- }
+OptionDefinition::factoryEmpty(Option::Universe u, uint16_t type) {
OptionPtr option(new Option(u, type));
return (option);
}
OptionPtr
-OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
- OptionPtr option(new Option(u, type, buf));
+OptionDefinition::factoryGeneric(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new Option(u, type, begin, end));
return (option);
}
OptionPtr
-OptionDefinition::factoryIA6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
- sanityCheckUniverse(u, Option::V6);
- if (buf.size() != Option6IA::OPTION6_IA_LEN) {
- isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
- << Option6IA::OPTION6_IA_LEN << " bytes");
+OptionDefinition::factoryIA6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < Option6IA::OPTION6_IA_LEN) {
+ isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
+ "at least " << Option6IA::OPTION6_IA_LEN << " bytes");
}
- boost::shared_ptr<Option6IA> option(new Option6IA(type, buf.begin(),
- buf.begin() + buf.size()));
+ boost::shared_ptr<Option6IA> option(new Option6IA(type, begin, end));
return (option);
}
OptionPtr
-OptionDefinition::factoryIAAddr6(Option::Universe u, uint16_t type, const OptionBuffer& buf) {
- sanityCheckUniverse(u, Option::V6);
- if (buf.size() != Option6IAAddr::OPTION6_IAADDR_LEN) {
- isc_throw(isc::OutOfRange, "input option buffer has invalid size, expeted "
- << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
+OptionDefinition::factoryIAAddr6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ if (std::distance(begin, end) < Option6IAAddr::OPTION6_IAADDR_LEN) {
+ isc_throw(isc::OutOfRange, "input option buffer has invalid size, expected "
+ " at least " << Option6IAAddr::OPTION6_IAADDR_LEN << " bytes");
}
- boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, buf.begin(),
- buf.begin() + buf.size()));
+ boost::shared_ptr<Option6IAAddr> option(new Option6IAAddr(type, begin, end));
return (option);
}
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index d9b7f98..efcaba0 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -27,31 +27,54 @@
namespace isc {
namespace dhcp {
+/// @brief Exception to be thrown when invalid option value has been
+/// specified for a particular option definition.
+class InvalidOptionValue : public Exception {
+public:
+ InvalidOptionValue(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when option definition is invalid.
+class MalformedOptionDefinition : public Exception {
+public:
+ MalformedOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+ DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Forward declaration to OptionDefinition.
class OptionDefinition;
/// @brief Pointer to option definition object.
typedef boost::shared_ptr<OptionDefinition> OptionDefinitionPtr;
-/// @brief Forward declaration to Option6Int.
+/// @brief Forward declaration to OptionInt.
///
-/// This forward declaration is needed to access Option6Int class
-/// without having to include option6_int.h header. This is because
-/// this header includes libdhcp++.h and this causes circular
-/// inclusion between libdhcp++.h, option_definition.h and
+/// This forward declaration is needed to access the OptionInt class without
+/// having to include the option_int.h header file. It is required because
+/// this header includes libdhcp++.h, and including option_int.h would cause
+/// circular inclusion between libdhcp++.h, option_definition.h and
/// option6_int.h.
template<typename T>
-class Option6Int;
+class OptionInt;
-/// @brief Forward declaration to Option6IntArray.
+/// @brief Forward declaration to OptionIntArray.
///
-/// This forward declaration is needed to access Option6IntArray class
-/// without having to include option6_int_array.h header. This is because
-/// this header includes libdhcp++.h and this causes circular
-/// inclusion between libdhcp++.h, option_definition.h and
-/// option6_int_array.h.
+/// This forward declaration is needed to access the OptionIntArray class
+/// without having to include the option_int_array.h header file. It is
+/// required because this header includes libdhcp++.h, and including
+/// option_int_array.h would cause circular inclusion between libdhcp++.h,
+/// option_definition.h and option_int_array.h.
template<typename T>
-class Option6IntArray;
+class OptionIntArray;
/// @brief Base class representing a DHCP option definition.
///
@@ -82,7 +105,7 @@ class Option6IntArray;
///
/// Should the option comprise data fields of different types, the "record"
/// option type is used. In such cases the data field types within the record
-/// are specified using \ref OptioDefinition::addRecordField.
+/// are specified using \ref OptionDefinition::addRecordField.
///
/// When the OptionDefinition object has been sucessfully created, it can be
/// queried to return the appropriate option factory function for the specified
@@ -111,73 +134,11 @@ class Option6IntArray;
class OptionDefinition {
public:
- /// Data types of DHCP option fields.
- enum DataType {
- EMPTY_TYPE,
- BINARY_TYPE,
- BOOLEAN_TYPE,
- INT8_TYPE,
- INT16_TYPE,
- INT32_TYPE,
- UINT8_TYPE,
- UINT16_TYPE,
- UINT32_TYPE,
- IPV4_ADDRESS_TYPE,
- IPV6_ADDRESS_TYPE,
- STRING_TYPE,
- FQDN_TYPE,
- RECORD_TYPE,
- UNKNOWN_TYPE
- };
-
/// List of fields within the record.
- typedef std::vector<DataType> RecordFieldsCollection;
+ typedef std::vector<OptionDataType> RecordFieldsCollection;
/// Const iterator for record data fields.
- typedef std::vector<DataType>::const_iterator RecordFieldsConstIter;
-
-private:
+ typedef std::vector<OptionDataType>::const_iterator RecordFieldsConstIter;
- /// @brief Utility class for operations on DataTypes.
- ///
- /// This class is implemented as the singleton because the list of
- /// supported data types need only be loaded only once into memory as it
- /// can persist for all option definitions.
- ///
- /// @todo This class can be extended to return the string value
- /// representing the data type from the enum value.
- class DataTypeUtil {
- public:
-
- /// @brief Return the sole instance of this class.
- ///
- /// @return instance of this class.
- static DataTypeUtil& instance() {
- static DataTypeUtil instance;
- return (instance);
- }
-
- /// @brief Convert type given as string value to option data type.
- ///
- /// @param data_type_name data type string.
- ///
- /// @return option data type.
- DataType getDataType(const std::string& data_type_name);
-
- private:
- /// @brief Private constructor.
- ///
- /// Constructor initializes the internal data structures, e.g.
- /// mapping between data type name and the corresponding enum.
- /// This constructor is private to ensure that exactly one
- /// instance of this class can be created using \ref instance
- /// function.
- DataTypeUtil();
-
- /// Map of data types, maps name of the type to enum value.
- std::map<std::string, DataType> data_types_;
- };
-
-public:
/// @brief Constructor.
///
/// @param name option name.
@@ -199,7 +160,7 @@ public:
/// option fields are the array.
OptionDefinition(const std::string& name,
const uint16_t code,
- const DataType type,
+ const OptionDataType type,
const bool array_type = false);
/// @brief Adds data field to the record.
@@ -216,7 +177,7 @@ public:
///
/// @throw isc::InvalidOperation if option type is not set to RECORD_TYPE.
/// @throw isc::BadValue if specified invalid data type.
- void addRecordField(const DataType data_type);
+ void addRecordField(const OptionDataType data_type);
/// @brief Return array type indicator.
///
@@ -231,13 +192,6 @@ public:
/// @return option code.
uint16_t getCode() const { return (code_); }
- /// @brief Return factory function for the given definition.
- ///
- /// @throw isc::OutOfRange if \ref validate returns it.
- /// @throw isc::BadValue if \ref validate returns it.
- /// @return pointer to a factory function.
- Option::Factory* getFactory() const;
-
/// @brief Return option name.
///
/// @return option name.
@@ -251,13 +205,11 @@ public:
/// @brief Return option data type.
///
/// @return option data type.
- DataType getType() const { return (type_); };
+ OptionDataType getType() const { return (type_); };
/// @brief Check if the option definition is valid.
///
- /// @throw isc::OutOfRange if invalid option type was specified.
- /// @throw isc::BadValue if invalid option name was specified,
- /// e.g. empty or containing spaces.
+ /// @throw MalformedOptionDefinition option definition is invalid.
void validate() const;
/// @brief Check if specified format is IA_NA option format.
@@ -270,101 +222,166 @@ public:
/// @return true if specified format is IAADDR option format.
bool haveIAAddr6Format() const;
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using
+ /// provided chunk of buffer. This function may be used to
+ /// create option which is to be sent in the outgoing packet.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin beginning of the option buffer.
+ /// @param end end of the option buffer.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw MalformedOptionDefinition if option definition is invalid.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) const;
+
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using
+ /// whole provided buffer. This function may be used to
+ /// create option which is to be sent in the outgoing packet.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param buf option buffer.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw MalformedOptionDefinition if option definition is invalid.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ const OptionBuffer& buf = OptionBuffer()) const;
+
+ /// @brief Option factory.
+ ///
+ /// This function creates an instance of DHCP option using the vector
+ /// of strings which carry data values for option data fields.
+ /// The order of values in the vector corresponds to the order of data
+ /// fields in the option. The supplied string values are cast to
+ /// their actual data types which are determined based on the
+ /// option definition. If cast fails due to type mismatch, an exception
+ /// is thrown. This factory function can be used to create option
+ /// instance when user specified option value in the <b>comma separated
+ /// values</b> format in the configuration database. Provided string
+ /// must be tokenized into the vector of string values and this vector
+ /// can be supplied to this function.
+ ///
+ /// @param u option universe (V4 or V6).
+ /// @param type option type.
+ /// @param values a vector of values to be used to set data for an option.
+ ///
+ /// @return instance of the DHCP option.
+ /// @throw MalformedOptionDefinition if option definition is invalid.
+ /// @throw InvalidOptionValue if data for the option is invalid.
+ OptionPtr optionFactory(Option::Universe u, uint16_t type,
+ const std::vector<std::string>& values) const;
+
/// @brief Factory to create option with address list.
///
- /// @param u universe (must be V4).
/// @param type option type.
- /// @param buf option buffer with a list of IPv4 addresses.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of IPv4 addresses.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of IPv4 addresses.
///
/// @throw isc::OutOfRange if length of the provided option buffer
/// is not multiple of IPV4 address length.
- static OptionPtr factoryAddrList4(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ static OptionPtr factoryAddrList4(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
/// @brief Factory to create option with address list.
///
- /// @param u universe (must be V6).
/// @param type option type.
- /// @param buf option buffer with a list of IPv6 addresses.
+ /// @param begin iterator pointing to the beginning of the buffer
+ /// with a list of IPv6 addresses.
+ /// @param end iterator pointing to the end of the buffer with
+ /// a list of IPv6 addresses.
///
/// @throw isc::OutOfaRange if length of provided option buffer
/// is not multiple of IPV6 address length.
- static OptionPtr factoryAddrList6(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ static OptionPtr factoryAddrList6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
/// @brief Empty option factory.
///
/// @param u universe (V6 or V4).
/// @param type option type.
- /// @param buf option buffer (must be empty).
- static OptionPtr factoryEmpty(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ static OptionPtr factoryEmpty(Option::Universe u, uint16_t type);
/// @brief Factory to create generic option.
///
/// @param u universe (V6 or V4).
/// @param type option type.
- /// @param buf option buffer.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
static OptionPtr factoryGeneric(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
/// @brief Factory for IA-type of option.
///
- /// @param u universe (must be V6).
/// @param type option type.
- /// @param buf option buffer.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
///
/// @throw isc::OutOfRange if provided option buffer is too short or
/// too long. Expected size is 12 bytes.
/// @throw isc::BadValue if specified universe value is not V6.
- static OptionPtr factoryIA6(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ static OptionPtr factoryIA6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
/// @brief Factory for IAADDR-type of option.
///
- /// @param u universe (must be V6).
/// @param type option type.
- /// @param buf option buffer.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
///
/// @throw isc::OutOfRange if provided option buffer is too short or
/// too long. Expected size is 24 bytes.
/// @throw isc::BadValue if specified universe value is not V6.
- static OptionPtr factoryIAAddr6(Option::Universe u, uint16_t type,
- const OptionBuffer& buf);
+ static OptionPtr factoryIAAddr6(uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end);
/// @brief Factory function to create option with integer value.
///
+ /// @param u universe (V4 or V6).
/// @param type option type.
- /// @param buf option buffer.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
/// @tparam T type of the data field (must be one of the uintX_t or intX_t).
///
/// @throw isc::OutOfRange if provided option buffer length is invalid.
template<typename T>
- static OptionPtr factoryInteger(Option::Universe, uint16_t type, const OptionBuffer& buf) {
- if (buf.size() > sizeof(T)) {
- isc_throw(isc::OutOfRange, "provided option buffer is too large, expected: "
- << sizeof(T) << " bytes");
- }
- OptionPtr option(new Option6Int<T>(type, buf.begin(), buf.end()));
+ static OptionPtr factoryInteger(Option::Universe u, uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new OptionInt<T>(u, type, begin, end));
return (option);
}
/// @brief Factory function to create option with array of integer values.
///
+ /// @param u universe (V4 or V6).
/// @param type option type.
- /// @param buf option buffer.
+ /// @param begin iterator pointing to the beginning of the buffer.
+ /// @param end iterator pointing to the end of the buffer.
/// @tparam T type of the data field (must be one of the uintX_t or intX_t).
///
/// @throw isc::OutOfRange if provided option buffer length is invalid.
template<typename T>
- static OptionPtr factoryIntegerArray(Option::Universe, uint16_t type, const OptionBuffer& buf) {
- if (buf.size() == 0) {
- isc_throw(isc::OutOfRange, "option buffer length must be greater than zero");
- } else if (buf.size() % OptionDataTypes<T>::len != 0) {
- isc_throw(isc::OutOfRange, "option buffer length must be multiple of "
- << OptionDataTypes<T>::len << " bytes");
- }
- OptionPtr option(new Option6IntArray<T>(type, buf.begin(), buf.end()));
+ static OptionPtr factoryIntegerArray(Option::Universe u,
+ uint16_t type,
+ OptionBufferConstIter begin,
+ OptionBufferConstIter end) {
+ OptionPtr option(new OptionIntArray<T>(u, type, begin, end));
return (option);
}
@@ -379,15 +396,49 @@ private:
/// @param first_type type of the first data field.
///
/// @return true if actual option format matches expected format.
- bool haveIAx6Format(const OptionDefinition::DataType first_type) const;
+ bool haveIAx6Format(const OptionDataType first_type) const;
/// @brief Check if specified type matches option definition type.
///
/// @return true if specified type matches option definition type.
- inline bool haveType(const DataType type) const {
+ inline bool haveType(const OptionDataType type) const {
return (type == type_);
}
+ /// @brief Perform lexical cast of the value and validate its range.
+ ///
+ /// This function performs lexical cast of a string value to integer
+ /// or boolean value and checks if the resulting value is within a
+ /// range of a target type. Note that range checks are not performed
+ /// on boolean values. The target type should be one of the supported
+ /// integer types or bool.
+ ///
+ /// @param value_str input value given as string.
+ /// @tparam T target type for lexical cast.
+ ///
+ /// @return cast value.
+ /// @throw BadDataTypeCast if cast was not successful.
+ template<typename T>
+ T lexicalCastWithRangeCheck(const std::string& value_str) const;
+
+ /// @brief Write the string value into the provided buffer.
+ ///
+ /// This method writes the given value to the specified buffer.
+ /// The provided string value may represent data of different types.
+ /// The actual data type is specified with the second argument.
+ /// Based on a value of this argument, this function will first
+ /// try to cast the string value to the particular data type and
+ /// if it is successful it will store the data in the buffer
+ /// in a binary format.
+ ///
+ /// @param value string representation of the value to be written.
+ /// @param type the actual data type to be stored.
+ /// @param [in, out] buf buffer where the value is to be stored.
+ ///
+ /// @throw BadDataTypeCast if data write was unsuccessful.
+ void writeToBuffer(const std::string& value, const OptionDataType type,
+ OptionBuffer& buf) const;
+
/// @brief Sanity check universe value.
///
/// @param expected_universe expected universe value.
@@ -395,14 +446,14 @@ private:
///
/// @throw isc::BadValue if expected universe and actual universe don't match.
static inline void sanityCheckUniverse(const Option::Universe expected_universe,
- const Option::Universe actual_universe);
+ const Option::Universe actual_universe);
/// Option name.
std::string name_;
/// Option code.
uint16_t code_;
/// Option data type.
- DataType type_;
+ OptionDataType type_;
/// Indicates wheter option is a single value or array.
bool array_type_;
/// Collection of data fields within the record.
@@ -421,7 +472,7 @@ private:
/// Note that this container can hold multiple options with the
/// same code. For this reason, the latter index can be used to
/// obtain a range of options for a particular option code.
-///
+///
/// @todo: need an index to search options using option space name
/// once option spaces are implemented.
typedef boost::multi_index_container<
@@ -449,6 +500,9 @@ typedef boost::multi_index_container<
>
> OptionDefContainer;
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
/// Type of the index #1 - option type.
typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
/// Pair of iterators to represent the range of options definitions
diff --git a/src/lib/dhcp/option_int.h b/src/lib/dhcp/option_int.h
new file mode 100644
index 0000000..ebb1641
--- /dev/null
+++ b/src/lib/dhcp/option_int.h
@@ -0,0 +1,194 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_INT_H
+#define OPTION_INT_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// This template class represents DHCP option with single value.
+/// This value is of integer type and can be any of the following:
+/// - uint8_t,
+/// - uint16_t,
+/// - uint32_t,
+/// - int8_t,
+/// - int16_t,
+/// - int32_t.
+///
+/// @param T data field type (see above).
+template<typename T>
+class OptionInt: public Option {
+
+public:
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option type.
+ /// @param value option value.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionInt(Option::Universe u, uint16_t type, T value)
+ : Option(u, type), value_(value) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This construtor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param type option type.
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionInt(Option::Universe u, uint16_t type, OptionBufferConstIter begin,
+ OptionBufferConstIter end)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ unpack(begin, end);
+ }
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ void pack(isc::util::OutputBuffer& buf) {
+ // Pack option header.
+ packHeader(buf);
+ // Depending on the data type length we use different utility functions
+ // writeUint16 or writeUint32 which write the data in the network byte
+ // order to the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.writeUint8(value_);
+ break;
+ case 2:
+ buf.writeUint16(value_);
+ break;
+ case 4:
+ buf.writeUint32(value_);
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ packOptions(buf);
+ }
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::OutOfRange if provided buffer is shorter than data size.
+ /// @throw isc::dhcp::InvalidDataType if size of a data field type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (distance(begin, end) < sizeof(T)) {
+ isc_throw(OutOfRange, "Option " << getType() << " truncated");
+ }
+ // @todo consider what to do if buffer is longer than data type.
+
+ // Depending on the data type length we use different utility functions
+ // readUint16 or readUint32 which read the data laid in the network byte
+ // order from the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ int data_size_len = OptionDataTypeTraits<T>::len;
+ switch (data_size_len) {
+ case 1:
+ value_ = *begin;
+ break;
+ case 2:
+ value_ = isc::util::readUint16(&(*begin));
+ break;
+ case 4:
+ value_ = isc::util::readUint32(&(*begin));
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ // Use local variable to set a new value for this iterator.
+ // When using OptionDataTypeTraits<T>::len directly some versions
+ // of clang complain about unresolved reference to
+ // OptionDataTypeTraits structure during linking.
+ begin += data_size_len;
+ unpackOptions(OptionBuffer(begin, end));
+ }
+
+ /// @brief Set option value.
+ ///
+ /// @param value new option value.
+ void setValue(T value) { value_ = value; }
+
+ /// @brief Return option value.
+ ///
+ /// @return option value.
+ T getValue() const { return value_; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() {
+ // Calculate the length of the header.
+ uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
+ // The data length is equal to size of T.
+ length += sizeof(T);;
+ // length of all suboptions
+ for (Option::OptionCollection::iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+ }
+
+private:
+
+ T value_; ///< Value conveyed by the option.
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_H
diff --git a/src/lib/dhcp/option_int_array.h b/src/lib/dhcp/option_int_array.h
new file mode 100644
index 0000000..5004152
--- /dev/null
+++ b/src/lib/dhcp/option_int_array.h
@@ -0,0 +1,234 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_INT_ARRAY_H
+#define OPTION_INT_ARRAY_H
+
+#include <dhcp/libdhcp++.h>
+#include <dhcp/option.h>
+#include <dhcp/option_data_types.h>
+#include <util/io_utilities.h>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dhcp {
+
+/// This template class represents DHCP (v4 or v6) option with an
+/// array of integer values. The type of the elements in the array
+/// can be any of the following:
+/// - uint8_t,
+/// - uint16_t,
+/// - uint32_t,
+/// - int8_t,
+/// - int16_t,
+/// - int32_t.
+///
+/// @warning Since this option may convey variable number of integer
+/// values, sub-options are should not be added in this option as
+/// there is no way to distinguish them from other data. The API will
+/// allow addition of sub-options but they will be ignored during
+/// packing and unpacking option data.
+///
+/// @param T data field type (see above).
+template<typename T>
+class OptionIntArray: public Option {
+
+public:
+
+ /// @brief Constructor.
+ ///
+ /// Creates option with empty values vector.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ ///
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type)
+ : Option(u, type),
+ values_(0) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ }
+
+ /// @brief Constructor.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param buf buffer with option data (must not be empty).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is empty or its length
+ /// is not multiple of size of the data type in bytes.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type,
+ const OptionBuffer& buf)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ unpack(buf.begin(), buf.end());
+ }
+
+ /// @brief Constructor.
+ ///
+ /// This constructor creates option from a buffer. This construtor
+ /// may throw exception if \ref unpack function throws during buffer
+ /// parsing.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @param type option type.
+ /// @param begin iterator to first byte of option data.
+ /// @param end iterator to end of option data (first byte after option end).
+ ///
+ /// @throw isc::OutOfRange if provided buffer is empty or its length
+ /// is not multiple of size of the data type in bytes.
+ /// @throw isc::dhcp::InvalidDataType if data field type provided
+ /// as template parameter is not a supported integer type.
+ OptionIntArray(const Option::Universe u, const uint16_t type,
+ OptionBufferConstIter begin, OptionBufferConstIter end)
+ : Option(u, type) {
+ if (!OptionDataTypeTraits<T>::integer_type) {
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ unpack(begin, end);
+ }
+
+ /// Writes option in wire-format to buf, returns pointer to first unused
+ /// byte after stored option.
+ ///
+ /// @param [out] buf buffer (option will be stored here)
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ void pack(isc::util::OutputBuffer& buf) {
+ // Pack option header.
+ packHeader(buf);
+ // Pack option data.
+ for (int i = 0; i < values_.size(); ++i) {
+ // Depending on the data type length we use different utility functions
+ // writeUint16 or writeUint32 which write the data in the network byte
+ // order to the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ switch (OptionDataTypeTraits<T>::len) {
+ case 1:
+ buf.writeUint8(values_[i]);
+ break;
+ case 2:
+ buf.writeUint16(values_[i]);
+ break;
+ case 4:
+ buf.writeUint32(values_[i]);
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ }
+ // We don't pack sub-options here because we have array-type option.
+ // We don't allow sub-options in array-type options as there is no
+ // way to distinguish them from the data fields on option reception.
+ }
+
+ /// @brief Parses received buffer
+ ///
+ /// Parses received buffer and returns offset to the first unused byte after
+ /// parsed option.
+ ///
+ /// @param begin iterator to first byte of option data
+ /// @param end iterator to end of option data (first byte after option end)
+ ///
+ /// @throw isc::dhcp::InvalidDataType if size of a data fields type is not
+ /// equal to 1, 2 or 4 bytes. The data type is not checked in this function
+ /// because it is checked in a constructor.
+ virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end) {
+ if (distance(begin, end) == 0) {
+ isc_throw(OutOfRange, "option " << getType() << " empty");
+ }
+ if (distance(begin, end) % sizeof(T) != 0) {
+ isc_throw(OutOfRange, "option " << getType() << " truncated");
+ }
+ // @todo consider what to do if buffer is longer than data type.
+
+ values_.clear();
+ while (begin != end) {
+ // Depending on the data type length we use different utility functions
+ // readUint16 or readUint32 which read the data laid in the network byte
+ // order from the provided buffer. The same functions can be safely used
+ // for either unsigned or signed integers so there is not need to create
+ // special cases for intX_t types.
+ int data_size_len = OptionDataTypeTraits<T>::len;
+ switch (data_size_len) {
+ case 1:
+ values_.push_back(*begin);
+ break;
+ case 2:
+ values_.push_back(isc::util::readUint16(&(*begin)));
+ break;
+ case 4:
+ values_.push_back(isc::util::readUint32(&(*begin)));
+ break;
+ default:
+ isc_throw(dhcp::InvalidDataType, "non-integer type");
+ }
+ // Use local variable to set a new value for this iterator.
+ // When using OptionDataTypeTraits<T>::len directly some versions
+ // of clang complain about unresolved reference to
+ // OptionDataTypeTraits structure during linking.
+ begin += data_size_len;
+ }
+ // We do not unpack sub-options here because we have array-type option.
+ // Such option have variable number of data fields, thus there is no
+ // way to assess where sub-options start.
+ }
+
+ /// @brief Return collection of option values.
+ ///
+ /// @return collection of values.
+ const std::vector<T>& getValues() const { return (values_); }
+
+ /// @brief Set option values.
+ ///
+ /// @param values collection of values to be set for option.
+ void setValues(const std::vector<T>& values) { values_ = values; }
+
+ /// @brief returns complete length of option
+ ///
+ /// Returns length of this option, including option header and suboptions
+ ///
+ /// @return length of this option
+ virtual uint16_t len() {
+ uint16_t length = (getUniverse() == Option::V4) ? OPTION4_HDR_LEN : OPTION6_HDR_LEN;
+ length += values_.size() * sizeof(T);
+ // length of all suboptions
+ for (Option::OptionCollection::iterator it = options_.begin();
+ it != options_.end();
+ ++it) {
+ length += (*it).second->len();
+ }
+ return (length);
+ }
+
+private:
+
+ std::vector<T> values_;
+};
+
+} // isc::dhcp namespace
+} // isc namespace
+
+#endif // OPTION_INT_ARRAY_H
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 5be8211..d3b22de 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -15,9 +15,11 @@
#include <asiolink/io_address.h>
#include <dhcp/dhcp4.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/option_int.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
+#include <algorithm>
#include <iostream>
#include <sstream>
@@ -38,8 +40,7 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(DHCPTypeToBootpType(msg_type)),
- htype_(HTYPE_ETHER),
- hlen_(0),
+ hwaddr_(new HWAddr()),
hops_(0),
transid_(transid),
secs_(0),
@@ -48,12 +49,12 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(DHCPV4_PKT_HDR_LEN),
- msg_type_(msg_type)
+ bufferOut_(DHCPV4_PKT_HDR_LEN)
{
- memset(chaddr_, 0, MAX_CHADDR_LEN);
memset(sname_, 0, MAX_SNAME_LEN);
memset(file_, 0, MAX_FILE_LEN);
+
+ setType(msg_type);
}
Pkt4::Pkt4(const uint8_t* data, size_t len)
@@ -64,6 +65,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(BOOTREQUEST),
+ hwaddr_(new HWAddr()),
transid_(0),
secs_(0),
flags_(0),
@@ -71,13 +73,15 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(0), // not used, this is RX packet
- msg_type_(DHCPDISCOVER)
+ bufferOut_(0) // not used, this is RX packet
{
if (len < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
<< ") received, at least " << DHCPV4_PKT_HDR_LEN
<< " is expected.");
+
+ } else if (data == NULL) {
+ isc_throw(InvalidParameter, "data buffer passed to Pkt4 is NULL");
}
data_.resize(len);
@@ -100,9 +104,15 @@ Pkt4::len() {
bool
Pkt4::pack() {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
+ }
+
+ size_t hw_len = hwaddr_->hwaddr_.size();
+
bufferOut_.writeUint8(op_);
- bufferOut_.writeUint8(htype_);
- bufferOut_.writeUint8(hlen_);
+ bufferOut_.writeUint8(hwaddr_->htype_);
+ bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
bufferOut_.writeUint8(hops_);
bufferOut_.writeUint32(transid_);
bufferOut_.writeUint16(secs_);
@@ -111,7 +121,23 @@ Pkt4::pack() {
bufferOut_.writeUint32(yiaddr_);
bufferOut_.writeUint32(siaddr_);
bufferOut_.writeUint32(giaddr_);
- bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+
+ if (hw_len <= MAX_CHADDR_LEN) {
+ // write up to 16 bytes of the hardware address (CHADDR field is 16
+ // bytes long in DHCPv4 message).
+ bufferOut_.writeData(&hwaddr_->hwaddr_[0],
+ (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
+ hw_len = MAX_CHADDR_LEN - hw_len;
+ } else {
+ hw_len = MAX_CHADDR_LEN;
+ }
+
+ // write (len) bytes of padding
+ vector<uint8_t> zeros(hw_len, 0);
+ bufferOut_.writeData(&zeros[0], hw_len);
+ // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
bufferOut_.writeData(sname_, MAX_SNAME_LEN);
bufferOut_.writeData(file_, MAX_FILE_LEN);
@@ -140,8 +166,8 @@ Pkt4::unpack() {
}
op_ = bufferIn.readUint8();
- htype_ = bufferIn.readUint8();
- hlen_ = bufferIn.readUint8();
+ uint8_t htype = bufferIn.readUint8();
+ uint8_t hlen = bufferIn.readUint8();
hops_ = bufferIn.readUint8();
transid_ = bufferIn.readUint32();
secs_ = bufferIn.readUint16();
@@ -150,15 +176,21 @@ Pkt4::unpack() {
yiaddr_ = IOAddress(bufferIn.readUint32());
siaddr_ = IOAddress(bufferIn.readUint32());
giaddr_ = IOAddress(bufferIn.readUint32());
- bufferIn.readData(chaddr_, MAX_CHADDR_LEN);
+
+ vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
+ bufferIn.readVector(hw_addr, MAX_CHADDR_LEN);
bufferIn.readData(sname_, MAX_SNAME_LEN);
bufferIn.readData(file_, MAX_FILE_LEN);
+ hw_addr.resize(hlen);
+
+ hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
+
if (bufferIn.getLength() == bufferIn.getPosition()) {
// this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
// particular, it does not have magic cookie, a 4 byte sequence that
// differentiates between DHCP and BOOTP packets.
- isc_throw(InvalidOperation, "Recevied BOOTP packet. BOOTP is not supported.");
+ isc_throw(InvalidOperation, "Received BOOTP packet. BOOTP is not supported.");
}
if (bufferIn.getLength() - bufferIn.getPosition() < 4) {
@@ -178,25 +210,50 @@ Pkt4::unpack() {
bufferIn.readVector(optsBuffer, opts_len);
LibDHCP::unpackOptions4(optsBuffer, options_);
- // TODO: check will need to be called separately, so hooks can be called after
- // packet is parsed, but before its content is verified
+ // @todo check will need to be called separately, so hooks can be called
+ // after the packet is parsed, but before its content is verified
check();
}
void Pkt4::check() {
- boost::shared_ptr<Option> typeOpt = getOption(DHO_DHCP_MESSAGE_TYPE);
+ uint8_t msg_type = getType();
+ if (msg_type > DHCPLEASEACTIVE) {
+ isc_throw(BadValue, "Invalid DHCP message type received: "
+ << msg_type);
+ }
+}
+
+uint8_t Pkt4::getType() const {
+ OptionPtr generic = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (!generic) {
+ isc_throw(Unexpected, "Missing DHCP Message Type option");
+ }
+
+ // Check if Message Type is specified as OptionInt<uint8_t>
+ boost::shared_ptr<OptionInt<uint8_t> > typeOpt =
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
if (typeOpt) {
- uint8_t msg_type = typeOpt->getUint8();
- if (msg_type>DHCPLEASEACTIVE) {
- isc_throw(BadValue, "Invalid DHCP message type received:" << msg_type);
- }
- msg_type_ = msg_type;
+ return (typeOpt->getValue());
+ }
+
+ // Try to use it as generic option
+ return (generic->getUint8());
+}
+void Pkt4::setType(uint8_t dhcp_type) {
+ OptionPtr opt = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (opt) {
+ // There is message type option already, update it
+ opt->setUint8(dhcp_type);
} else {
- isc_throw(Unexpected, "Missing DHCP Message Type option");
+ // There is no message type option yet, add it
+ std::vector<uint8_t> tmp(1, dhcp_type);
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
+ addOption(opt);
}
}
+
void Pkt4::repack() {
bufferOut_.writeData(&data_[0], data_.size());
}
@@ -206,7 +263,7 @@ Pkt4::toText() {
stringstream tmp;
tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
<< " remoteAddr=" << remote_addr_.toText()
- << ":" << remote_port_ << ", msgtype=" << int(msg_type_)
+ << ":" << remote_port_ << ", msgtype=" << getType()
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
@@ -221,21 +278,26 @@ Pkt4::toText() {
void
Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
- const std::vector<uint8_t>& macAddr) {
- /// TODO Rewrite this once support for client-identifier option
+ const std::vector<uint8_t>& mac_addr) {
+ /// @todo Rewrite this once support for client-identifier option
/// is implemented (ticket 1228?)
- if (hlen>MAX_CHADDR_LEN) {
+ if (hlen > MAX_CHADDR_LEN) {
isc_throw(OutOfRange, "Hardware address (len=" << hlen
<< " too long. Max " << MAX_CHADDR_LEN << " supported.");
- }
- if ( (macAddr.size() == 0) && (hlen > 0) ) {
+
+ } else if (mac_addr.empty() && (hlen > 0) ) {
isc_throw(OutOfRange, "Invalid HW Address specified");
}
- htype_ = hType;
- hlen_ = hlen;
- memset(chaddr_, 0, MAX_CHADDR_LEN);
- memcpy(chaddr_, &macAddr[0], hlen);
+ hwaddr_.reset(new HWAddr(mac_addr, hType));
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+ }
+ hwaddr_ = addr;
}
void
@@ -243,11 +305,15 @@ Pkt4::setSname(const uint8_t* sname, size_t snameLen /*= MAX_SNAME_LEN*/) {
if (snameLen > MAX_SNAME_LEN) {
isc_throw(OutOfRange, "sname field (len=" << snameLen
<< ") too long, Max " << MAX_SNAME_LEN << " supported.");
+
+ } else if (sname == NULL) {
+ isc_throw(InvalidParameter, "Invalid sname specified");
}
- memset(sname_, 0, MAX_SNAME_LEN);
- memcpy(sname_, sname, snameLen);
- // no need to store snameLen as any empty space is filled with 0s
+ std::copy(&sname[0], &sname[snameLen], &sname_[0]);
+ std::fill(&sname_[snameLen], &sname_[MAX_SNAME_LEN], 0);
+
+ // No need to store snameLen as any empty space is filled with 0s
}
void
@@ -255,11 +321,15 @@ Pkt4::setFile(const uint8_t* file, size_t fileLen /*= MAX_FILE_LEN*/) {
if (fileLen > MAX_FILE_LEN) {
isc_throw(OutOfRange, "file field (len=" << fileLen
<< ") too long, Max " << MAX_FILE_LEN << " supported.");
+
+ } else if (file == NULL) {
+ isc_throw(InvalidParameter, "Invalid file name specified");
}
- memset(file_, 0, MAX_FILE_LEN);
- memcpy(file_, file, fileLen);
- // no need to store fileLen as any empty space is filled with 0s
+ std::copy(&file[0], &file[fileLen], &file_[0]);
+ std::fill(&file_[fileLen], &file_[MAX_FILE_LEN], 0);
+
+ // No need to store fileLen as any empty space is filled with 0s
}
uint8_t
@@ -272,6 +342,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
case DHCPINFORM:
case DHCPLEASEQUERY:
return (BOOTREQUEST);
+
case DHCPACK:
case DHCPNAK:
case DHCPOFFER:
@@ -279,15 +350,33 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
case DHCPLEASEUNKNOWN:
case DHCPLEASEACTIVE:
return (BOOTREPLY);
+
default:
isc_throw(OutOfRange, "Invalid message type: "
<< static_cast<int>(dhcpType) );
}
}
+uint8_t
+Pkt4::getHtype() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ return (hwaddr_->htype_);
+}
+
+uint8_t
+Pkt4::getHlen() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ uint8_t len = hwaddr_->hwaddr_.size();
+ return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
+}
+
void
Pkt4::addOption(boost::shared_ptr<Option> opt) {
- // check for uniqueness (DHCPv4 options must be unique)
+ // Check for uniqueness (DHCPv4 options must be unique)
if (getOption(opt->getType())) {
isc_throw(BadValue, "Option " << opt->getType()
<< " already present in this message.");
@@ -296,14 +385,24 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
}
boost::shared_ptr<isc::dhcp::Option>
-Pkt4::getOption(uint8_t type) {
+Pkt4::getOption(uint8_t type) const {
Option::OptionCollection::const_iterator x = options_.find(type);
- if (x!=options_.end()) {
+ if (x != options_.end()) {
return (*x).second;
}
return boost::shared_ptr<isc::dhcp::Option>(); // NULL
}
+bool
+Pkt4::delOption(uint8_t type) {
+ isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ }
+ return (false); // can't find option to be deleted
+}
+
void
Pkt4::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index e09069c..664686b 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <util/buffer.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/shared_ptr.hpp>
@@ -89,7 +90,7 @@ public:
/// reasonable value. This method is expected to grow significantly.
/// It makes sense to separate unpack() and check() for testing purposes.
///
- /// TODO: It is called from unpack() directly. It should be separated.
+ /// @todo It is called from unpack() directly. It should be separated.
///
/// Method will throw exception if anomaly is found.
void check();
@@ -217,16 +218,15 @@ public:
/// @return transaction-id
uint32_t getTransid() const { return (transid_); };
- /// @brief Returns message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @return message type
- uint8_t
- getType() const { return (msg_type_); }
+ uint8_t getType() const;
- /// @brief Sets message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @param type message type to be set
- void setType(uint8_t type) { msg_type_=type; };
+ void setType(uint8_t type);
/// @brief Returns sname field
///
@@ -262,38 +262,39 @@ public:
/// @brief Sets hardware address.
///
/// Sets parameters of hardware address. hlen specifies
- /// length of macAddr buffer. Content of macAddr buffer
+ /// length of mac_addr buffer. Content of mac_addr buffer
/// will be copied to appropriate field.
///
- /// Note: macAddr must be a buffer of at least hlen bytes.
+ /// Note: mac_addr must be a buffer of at least hlen bytes.
///
/// @param hType hardware type (will be sent in htype field)
/// @param hlen hardware length (will be sent in hlen field)
- /// @param macAddr pointer to hardware address
+ /// @param mac_addr pointer to hardware address
void setHWAddr(uint8_t hType, uint8_t hlen,
- const std::vector<uint8_t>& macAddr);
+ const std::vector<uint8_t>& mac_addr);
+
+ /// @brief Sets hardware address
+ ///
+ /// Sets hardware address, based on existing HWAddr structure
+ /// @param addr already filled in HWAddr structure
+ /// @throw BadValue if addr is null
+ void setHWAddr(const HWAddrPtr& addr);
/// Returns htype field
///
/// @return hardware type
uint8_t
- getHtype() const { return (htype_); };
+ getHtype() const;
/// Returns hlen field
///
/// @return hardware address length
uint8_t
- getHlen() const { return (hlen_); };
-
- /// @brief Returns chaddr field.
- ///
- /// Note: This is 16 bytes long field. It doesn't have to be
- /// null-terminated. Do no use strlen() or similar on it.
- ///
- /// @return pointer to hardware address
- const uint8_t*
- getChaddr() const { return (chaddr_); };
+ getHlen() const;
+ /// @brief returns hardware address information
+ /// @return hardware address structure
+ HWAddrPtr getHWAddr() const { return (hwaddr_); }
/// @brief Returns reference to output buffer.
///
@@ -321,7 +322,12 @@ public:
/// @return returns option of requested type (or NULL)
/// if no such option is present
boost::shared_ptr<Option>
- getOption(uint8_t opt_type);
+ getOption(uint8_t opt_type) const;
+
+ /// @brief Deletes specified option
+ /// @param type option type to be deleted
+ /// @return true if anything was deleted, false otherwise
+ bool delOption(uint8_t type);
/// @brief Returns interface name.
///
@@ -367,7 +373,7 @@ public:
/// @brief Returns remote address
///
/// @return remote address
- const isc::asiolink::IOAddress& getRemoteAddr() {
+ const isc::asiolink::IOAddress& getRemoteAddr() const {
return (remote_addr_);
}
@@ -381,7 +387,7 @@ public:
/// @brief Returns local address.
///
/// @return local address
- const isc::asiolink::IOAddress& getLocalAddr() {
+ const isc::asiolink::IOAddress& getLocalAddr() const {
return (local_addr_);
}
@@ -393,7 +399,7 @@ public:
/// @brief Returns local port.
///
/// @return local port
- uint16_t getLocalPort() { return (local_port_); }
+ uint16_t getLocalPort() const { return (local_port_); }
/// @brief Sets remote port.
///
@@ -403,7 +409,7 @@ public:
/// @brief Returns remote port.
///
/// @return remote port
- uint16_t getRemotePort() { return (remote_port_); }
+ uint16_t getRemotePort() const { return (remote_port_); }
/// @brief Update packet timestamp.
///
@@ -454,11 +460,11 @@ protected:
/// type is kept in message type option).
uint8_t op_;
- /// link-layer address type
- uint8_t htype_;
-
- /// link-layer address length
- uint8_t hlen_;
+ /// @brief link-layer address and hardware information
+ /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the
+ /// hardware address, up to 16) and chaddr (hardware address field,
+ /// 16 bytes)
+ HWAddrPtr hwaddr_;
/// Number of relay agents traversed
uint8_t hops_;
@@ -484,9 +490,6 @@ protected:
/// giaddr field (32 bits): Gateway IP address
isc::asiolink::IOAddress giaddr_;
- /// Hardware address field (16 bytes)
- uint8_t chaddr_[MAX_CHADDR_LEN];
-
/// sname field (64 bytes)
uint8_t sname_[MAX_SNAME_LEN];
@@ -518,16 +521,11 @@ protected:
/// data format change etc.
std::vector<uint8_t> data_;
- /// message type (e.g. 1=DHCPDISCOVER)
- /// TODO: this will eventually be replaced with DHCP Message Type
- /// option (option 53)
- uint8_t msg_type_;
-
/// collection of options present in this message
///
- /// @warnig This protected member is accessed by derived
+ /// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+ /// @ref perfdhcp::PerfPkt4. The impact on derived classes'
/// behavior must be taken into consideration before making
/// changes to this member such as access scope restriction or
/// data format change etc.
diff --git a/src/lib/dhcp/pkt6.cc b/src/lib/dhcp/pkt6.cc
index 12597c3..2c97b07 100644
--- a/src/lib/dhcp/pkt6.cc
+++ b/src/lib/dhcp/pkt6.cc
@@ -81,13 +81,6 @@ Pkt6::pack() {
bool
Pkt6::packUDP() {
-
- // TODO: Once OutputBuffer is used here, some thing like this
- // will be used. Yikes! That's ugly.
- // bufferOut_.writeData(ciaddr_.getAddress().to_v6().to_bytes().data(), 16);
- // It is better to implement a method in IOAddress that extracts
- // vector<uint8_t>
-
try {
// DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
bufferOut_.writeUint8(msg_type_);
@@ -172,17 +165,30 @@ Pkt6::toText() {
return tmp.str();
}
-boost::shared_ptr<isc::dhcp::Option>
+OptionPtr
Pkt6::getOption(uint16_t opt_type) {
isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type);
if (x!=options_.end()) {
return (*x).second;
}
- return boost::shared_ptr<isc::dhcp::Option>(); // NULL
+ return OptionPtr(); // NULL
+}
+
+isc::dhcp::Option::OptionCollection
+Pkt6::getOptions(uint16_t opt_type) {
+ isc::dhcp::Option::OptionCollection found;
+
+ for (Option::OptionCollection::const_iterator x = options_.begin();
+ x != options_.end(); ++x) {
+ if (x->first == opt_type) {
+ found.insert(make_pair(opt_type, x->second));
+ }
+ }
+ return (found);
}
void
-Pkt6::addOption(boost::shared_ptr<Option> opt) {
+Pkt6::addOption(const OptionPtr& opt) {
options_.insert(pair<int, boost::shared_ptr<Option> >(opt->getType(), opt));
}
@@ -205,6 +211,52 @@ Pkt6::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
}
+const char*
+Pkt6::getName(uint8_t type) {
+ static const char* CONFIRM = "CONFIRM";
+ static const char* DECLINE = "DECLINE";
+ static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST";
+ static const char* REBIND = "REBIND";
+ static const char* RELEASE = "RELEASE";
+ static const char* RENEW = "RENEW";
+ static const char* REQUEST = "REQUEST";
+ static const char* SOLICIT = "SOLICIT";
+ static const char* UNKNOWN = "UNKNOWN";
+
+ switch (type) {
+ case DHCPV6_CONFIRM:
+ return (CONFIRM);
+
+ case DHCPV6_DECLINE:
+ return (DECLINE);
+
+ case DHCPV6_INFORMATION_REQUEST:
+ return (INFORMATION_REQUEST);
+
+ case DHCPV6_REBIND:
+ return (REBIND);
+
+ case DHCPV6_RELEASE:
+ return (RELEASE);
+
+ case DHCPV6_RENEW:
+ return (RENEW);
+
+ case DHCPV6_REQUEST:
+ return (REQUEST);
+
+ case DHCPV6_SOLICIT:
+ return (SOLICIT);
+
+ default:
+ ;
+ }
+ return (UNKNOWN);
+}
+
+const char* Pkt6::getName() const {
+ return (getName(getType()));
+}
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcp/pkt6.h b/src/lib/dhcp/pkt6.h
index 5782737..6ffea2b 100644
--- a/src/lib/dhcp/pkt6.h
+++ b/src/lib/dhcp/pkt6.h
@@ -127,7 +127,7 @@ public:
/// Returns message type (e.g. 1 = SOLICIT)
///
/// @return message type
- uint8_t getType() { return (msg_type_); }
+ uint8_t getType() const { return (msg_type_); }
/// Sets message type (e.g. 1 = SOLICIT)
///
@@ -147,19 +147,28 @@ public:
/// Adds an option to this packet.
///
/// @param opt option to be added.
- void addOption(OptionPtr opt);
+ void addOption(const OptionPtr& opt);
/// @brief Returns the first option of specified type.
///
/// Returns the first option of specified type. Note that in DHCPv6 several
/// instances of the same option are allowed (and frequently used).
- /// See getOptions().
+ /// Also see \ref getOptions().
///
/// @param type option type we are looking for
///
/// @return pointer to found option (or NULL)
OptionPtr getOption(uint16_t type);
+ /// @brief Returns all instances of specified type.
+ ///
+ /// Returns all instances of options of the specified type. DHCPv6 protocol
+ /// allows (and uses frequently) multiple instances.
+ ///
+ /// @param type option type we are looking for
+ /// @return instance of option collection with requested options
+ isc::dhcp::Option::OptionCollection getOptions(uint16_t type);
+
/// Attempts to delete first suboption of requested type
///
/// @param type Type of option to be deleted.
@@ -180,7 +189,9 @@ public:
/// @brief Returns remote address
///
/// @return remote address
- const isc::asiolink::IOAddress& getRemoteAddr() { return (remote_addr_); }
+ const isc::asiolink::IOAddress& getRemoteAddr() const {
+ return (remote_addr_);
+ }
/// @brief Sets local address.
///
@@ -190,7 +201,9 @@ public:
/// @brief Returns local address.
///
/// @return local address
- const isc::asiolink::IOAddress& getLocalAddr() { return (local_addr_); }
+ const isc::asiolink::IOAddress& getLocalAddr() const {
+ return (local_addr_);
+ }
/// @brief Sets local port.
///
@@ -200,7 +213,7 @@ public:
/// @brief Returns local port.
///
/// @return local port
- uint16_t getLocalPort() { return (local_port_); }
+ uint16_t getLocalPort() const { return (local_port_); }
/// @brief Sets remote port.
///
@@ -210,7 +223,7 @@ public:
/// @brief Returns remote port.
///
/// @return remote port
- uint16_t getRemotePort() { return (remote_port_); }
+ uint16_t getRemotePort() const { return (remote_port_); }
/// @brief Sets interface index.
///
@@ -246,8 +259,6 @@ public:
/// @return interface name
void setIface(const std::string& iface ) { iface_ = iface; };
- /// TODO Need to implement getOptions() as well
-
/// collection of options present in this message
///
/// @warning This protected member is accessed by derived
@@ -266,6 +277,34 @@ public:
/// @throw isc::Unexpected if timestamp update failed
void updateTimestamp();
+ /// @brief Return textual type of packet.
+ ///
+ /// Returns the name of valid packet received by the server (e.g. SOLICIT).
+ /// If the packet is unknown - or if it is a valid DHCP packet but not one
+ /// expected to be received by the server (such as an ADVERTISE), the string
+ /// "UNKNOWN" is returned. This method is used in debug messages.
+ ///
+ /// As the operation of the method does not depend on any server state, it
+ /// is declared static. There is also non-static getName() method that
+ /// works on Pkt6 objects.
+ ///
+ /// @param type DHCPv6 packet type
+ ///
+ /// @return Pointer to "const" string containing the packet name.
+ /// Note that this string is statically allocated and MUST NOT
+ /// be freed by the caller.
+ static const char* getName(uint8_t type);
+
+ /// @brief returns textual representation of packet type.
+ ///
+ /// This method requires an object. There is also static version, which
+ /// requires one parameter (type).
+ ///
+ /// @return Pointer to "const" string containing packet name.
+ /// Note that this string is statically allocated and MUST NOT
+ /// be freed by the caller.
+ const char* getName() const;
+
protected:
/// Builds on wire packet for TCP transmission.
///
diff --git a/src/lib/dhcp/std_option_defs.h b/src/lib/dhcp/std_option_defs.h
new file mode 100644
index 0000000..144df8b
--- /dev/null
+++ b/src/lib/dhcp/std_option_defs.h
@@ -0,0 +1,329 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef STD_OPTION_DEFS_H
+#define STD_OPTION_DEFS_H
+
+#include <dhcp/option_data_types.h>
+
+namespace {
+
+/// @brief Declare an array holding parameters used to create instance
+/// of a definition for option comprising a record of data fields.
+///
+/// @param name name of the array being declared.
+/// @param types data types of fields that belong to the record.
+#ifndef RECORD_DECL
+#define RECORD_DECL(name, types...) const OptionDataType name[] = { types }
+#endif
+
+/// @brief A pair of values: one pointing to the array holding types of
+/// data fields belonging to the record, and size of this array.
+///
+/// @param name name of the array holding data fields' types.
+#ifndef RECORD_DEF
+#define RECORD_DEF(name) name, sizeof(name) / sizeof(name[0])
+#endif
+
+#ifndef NO_RECORD_DEF
+#define NO_RECORD_DEF 0, 0
+#endif
+
+using namespace isc::dhcp;
+
+/// @brief Parameters being used to make up an option definition.
+struct OptionDefParams {
+ const char* name; // option name
+ uint16_t code; // option code
+ OptionDataType type; // data type
+ bool array; // is array
+ const OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+};
+
+// fqdn option record fields.
+//
+// Note that the flags field indicates the type of domain
+// name encoding. There is a choice between deprecated
+// ASCII encoding and compressed encoding described in
+// RFC 1035, section 3.1. The latter could be handled
+// by OPT_FQDN_TYPE but we can't use it here because
+// clients may request ASCII encoding.
+RECORD_DECL(FQDN_RECORDS, OPT_UINT8_TYPE, OPT_UINT8_TYPE, OPT_STRING_TYPE);
+
+/// @brief Definitions of standard DHCPv4 options.
+const OptionDefParams OPTION_DEF_PARAMS4[] = {
+ { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "time-offset", DHO_TIME_OFFSET, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "routers", DHO_ROUTERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "time-servers", DHO_TIME_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "name-servers", DHO_NAME_SERVERS, OPT_IPV4_ADDRESS_TYPE,
+ false, NO_RECORD_DEF },
+ { "domain-name-servers", DHO_DOMAIN_NAME_SERVERS,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "log-servers", DHO_LOG_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "cookie-servers", DHO_COOKIE_SERVERS, OPT_IPV4_ADDRESS_TYPE,
+ true, NO_RECORD_DEF },
+ { "lpr-servers", DHO_LPR_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "impress-servers", DHO_IMPRESS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "resource-location-servers", DHO_RESOURCE_LOCATION_SERVERS,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "host-name", DHO_HOST_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "boot-size", DHO_BOOT_SIZE, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "merit-dump", DHO_MERIT_DUMP, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "domain-name", DHO_DOMAIN_NAME, OPT_FQDN_TYPE, false, NO_RECORD_DEF },
+ { "swap-server", DHO_SWAP_SERVER, OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "root-path", DHO_ROOT_PATH, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "extensions-path", DHO_EXTENSIONS_PATH, OPT_STRING_TYPE,
+ false, NO_RECORD_DEF },
+ { "ip-forwarding", DHO_IP_FORWARDING, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "non-local-source-routing", DHO_NON_LOCAL_SOURCE_ROUTING,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "policy-filter", DHO_POLICY_FILTER, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "max-dgram-reassembly", DHO_MAX_DGRAM_REASSEMBLY,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "default-ip-ttl", DHO_DEFAULT_IP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "path-mtu-aging-timeout", DHO_PATH_MTU_AGING_TIMEOUT,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "path-mtu-plateau-table", DHO_PATH_MTU_PLATEAU_TABLE,
+ OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ { "interface-mtu", DHO_INTERFACE_MTU, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "all-subnets-local", DHO_ALL_SUBNETS_LOCAL,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "broadcast-address", DHO_BROADCAST_ADDRESS,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "perform-mask-discovery", DHO_PERFORM_MASK_DISCOVERY,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "mask-supplier", DHO_MASK_SUPPLIER, OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "router-discovery", DHO_ROUTER_DISCOVERY,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "router-solicitation-address", DHO_ROUTER_SOLICITATION_ADDRESS,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "static-routes", DHO_STATIC_ROUTES,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "trailer-encapsulation", DHO_TRAILER_ENCAPSULATION,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "arp-cache-timeout", DHO_ARP_CACHE_TIMEOUT,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "ieee802-3-encapsulation", DHO_IEEE802_3_ENCAPSULATION,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "default-tcp-ttl", DHO_DEFAULT_TCP_TTL, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "tcp-keepalive-internal", DHO_TCP_KEEPALIVE_INTERVAL,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "tcp-keepalive-garbage", DHO_TCP_KEEPALIVE_GARBAGE,
+ OPT_BOOLEAN_TYPE, false, NO_RECORD_DEF },
+ { "nis-domain", DHO_NIS_DOMAIN, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "nis-servers", DHO_NIS_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "ntp-servers", DHO_NTP_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "vendor-encapsulated-options", DHO_VENDOR_ENCAPSULATED_OPTIONS,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "netbios-name-servers", DHO_NETBIOS_NAME_SERVERS,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "netbios-dd-server", DHO_NETBIOS_DD_SERVER,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "netbios-node-type", DHO_NETBIOS_NODE_TYPE,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "netbios-scope", DHO_NETBIOS_SCOPE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "font-servers", DHO_FONT_SERVERS, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "x-display-manager", DHO_X_DISPLAY_MANAGER,
+ OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "dhcp-requested-address", DHO_DHCP_REQUESTED_ADDRESS,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-lease-time", DHO_DHCP_LEASE_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-option-overload", DHO_DHCP_OPTION_OVERLOAD,
+ OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-message-type", DHO_DHCP_MESSAGE_TYPE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-server-identifier", DHO_DHCP_SERVER_IDENTIFIER,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-parameter-request-list", DHO_DHCP_PARAMETER_REQUEST_LIST,
+ OPT_UINT8_TYPE, true, NO_RECORD_DEF },
+ { "dhcp-message", DHO_DHCP_MESSAGE, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-max-message-size", DHO_DHCP_MAX_MESSAGE_SIZE,
+ OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-renewal-time", DHO_DHCP_RENEWAL_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-rebinding-time", DHO_DHCP_REBINDING_TIME,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "vendor-class-identifier", DHO_VENDOR_CLASS_IDENTIFIER,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "dhcp-client-identifier", DHO_DHCP_CLIENT_IDENTIFIER,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "nwip-domain-name", DHO_NWIP_DOMAIN_NAME, OPT_STRING_TYPE, false, NO_RECORD_DEF },
+ { "nwip-suboptions", DHO_NWIP_SUBOPTIONS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "user-class", DHO_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "fqdn", DHO_FQDN, OPT_RECORD_TYPE, false, RECORD_DEF(FQDN_RECORDS) },
+ { "dhcp-agent-options", DHO_DHCP_AGENT_OPTIONS,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ // Unfortunatelly the AUTHENTICATE option contains a 64-bit
+ // data field called 'replay-detection' that can't be added
+ // as a record field to a custom option. Also, there is no
+ // dedicated option class to handle it so we simply return
+ // binary option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "authenticate", DHO_AUTHENTICATE, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "client-last-transaction-time", DHO_CLIENT_LAST_TRANSACTION_TIME,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "associated-ip", DHO_ASSOCIATED_IP, OPT_IPV4_ADDRESS_TYPE, true, NO_RECORD_DEF },
+ { "subnet-selection", DHO_SUBNET_SELECTION,
+ OPT_IPV4_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ // The following options need a special encoding of data
+ // being carried by them. Therefore, there is no way they can
+ // be handled by OptionCustom. We may need to implement
+ // dedicated classes to handle them. Until that happens
+ // let's treat them as 'binary' options.
+ { "domain-search", DHO_DOMAIN_SEARCH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "vivco-suboptions", DHO_VIVCO_SUBOPTIONS,
+ OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "vivso-suboptions", DHO_VIVSO_SUBOPTIONS, OPT_BINARY_TYPE,
+ false, NO_RECORD_DEF }
+
+ // @todo add definitions for all remaning options.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE4 =
+ sizeof(OPTION_DEF_PARAMS4) / sizeof(OPTION_DEF_PARAMS4[0]);
+
+
+/// Start Definition of DHCPv6 options
+
+// client-fqdn
+RECORD_DECL(CLIENT_FQDN_RECORDS, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(GEOCONF_CIVIC_RECORDS, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+ OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(IAADDR_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(IA_NA_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(IA_PD_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(IA_PREFIX_RECORDS, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(LQ_QUERY_RECORDS, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(LQ_RELAY_DATA_RECORDS, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(REMOTE_ID_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(STATUS_CODE_RECORDS, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(VENDOR_CLASS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(VENDOR_OPTS_RECORDS, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+
+/// Standard DHCPv6 option definitions.
+///
+/// @warning in this array, the initializers are provided for all
+/// OptionDefParams struct's members despite initializers for
+/// 'records' and 'record_size' could be ommited for entries for
+/// which 'type' does not equal to OPT_RECORD_TYPE. If initializers
+/// are ommitted the corresponding values should default to 0.
+/// This however does not work on Solaris (GCC) which issues a
+/// warning about lack of initializers for some struct members
+/// causing build to fail.
+const OptionDefParams OPTION_DEF_PARAMS6[] = {
+ { "clientid", D6O_CLIENTID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "serverid", D6O_SERVERID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "ia-na", D6O_IA_NA, OPT_RECORD_TYPE, false, RECORD_DEF(IA_NA_RECORDS) },
+ { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(IAADDR_RECORDS) },
+ { "oro", D6O_ORO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ { "preference", D6O_PREFERENCE, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "elapsed-time", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false, NO_RECORD_DEF },
+ { "relay-msg", D6O_RELAY_MSG, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ // Unfortunatelly the AUTH option contains a 64-bit data field
+ // called 'replay-detection' that can't be added as a record
+ // field to a custom option. Also, there is no dedicated
+ // option class to handle it so we simply return binary
+ // option type for now.
+ // @todo implement a class to handle AUTH option.
+ { "auth", D6O_AUTH, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "unicast", D6O_UNICAST, OPT_IPV6_ADDRESS_TYPE, false, NO_RECORD_DEF },
+ { "status-code", D6O_STATUS_CODE, OPT_RECORD_TYPE, false,
+ RECORD_DEF(STATUS_CODE_RECORDS) },
+ { "rapid-commit", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+ { "user-class", D6O_USER_CLASS, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "vendor-class", D6O_VENDOR_CLASS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(VENDOR_CLASS_RECORDS) },
+ { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(VENDOR_OPTS_RECORDS) },
+ { "interface-id", D6O_INTERFACE_ID, OPT_BINARY_TYPE, false, NO_RECORD_DEF },
+ { "reconf-msg", D6O_RECONF_MSG, OPT_UINT8_TYPE, false, NO_RECORD_DEF },
+ { "reconf-accept", D6O_RECONF_ACCEPT, OPT_EMPTY_TYPE, false,
+ NO_RECORD_DEF },
+ { "sip-server-dns", D6O_SIP_SERVERS_DNS, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "sip-server-addr", D6O_SIP_SERVERS_ADDR, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "dns-servers", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "domain-search", D6O_DOMAIN_SEARCH, OPT_FQDN_TYPE, true, NO_RECORD_DEF },
+ { "ia-pd", D6O_IA_PD, OPT_RECORD_TYPE, false, RECORD_DEF(IA_PD_RECORDS) },
+ { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+ RECORD_DEF(IA_PREFIX_RECORDS) },
+ { "nis-servers", D6O_NIS_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "nisp-servers", D6O_NISP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "nis-domain-name", D6O_NIS_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "nisp-domain-name", D6O_NISP_DOMAIN_NAME, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "sntp-servers", D6O_SNTP_SERVERS, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "information-refresh-time", D6O_INFORMATION_REFRESH_TIME,
+ OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "bcmcs-server-dns", D6O_BCMCS_SERVER_D, OPT_FQDN_TYPE, true,
+ NO_RECORD_DEF },
+ { "bcmcs-server-addr", D6O_BCMCS_SERVER_A, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "geoconf-civic", D6O_GEOCONF_CIVIC, OPT_RECORD_TYPE, false,
+ RECORD_DEF(GEOCONF_CIVIC_RECORDS) },
+ { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+ RECORD_DEF(REMOTE_ID_RECORDS) },
+ { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF },
+ { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+ RECORD_DEF(CLIENT_FQDN_RECORDS) },
+ { "pana-agent", D6O_PANA_AGENT, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF },
+ { "new-posix-timezone", D6O_NEW_POSIX_TIMEZONE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF },
+ { "new-tzdb-timezone", D6O_NEW_TZDB_TIMEZONE, OPT_STRING_TYPE, false,
+ NO_RECORD_DEF },
+ { "ero", D6O_ERO, OPT_UINT16_TYPE, true, NO_RECORD_DEF },
+ { "lq-query", D6O_LQ_QUERY, OPT_RECORD_TYPE, false,
+ RECORD_DEF(LQ_QUERY_RECORDS) },
+ { "client-data", D6O_CLIENT_DATA, OPT_EMPTY_TYPE, false, NO_RECORD_DEF },
+ { "clt-time", D6O_CLT_TIME, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "lq-relay-data", D6O_LQ_RELAY_DATA, OPT_RECORD_TYPE, false,
+ RECORD_DEF(LQ_RELAY_DATA_RECORDS) },
+ { "lq-client-link", D6O_LQ_CLIENT_LINK, OPT_IPV6_ADDRESS_TYPE, true,
+ NO_RECORD_DEF }
+
+ // @todo There is still a bunch of options for which we have to provide
+ // definitions but we don't do it because they are not really
+ // critical right now.
+};
+
+/// Number of option definitions defined.
+const int OPTION_DEF_PARAMS_SIZE6 =
+ sizeof(OPTION_DEF_PARAMS6) / sizeof(OPTION_DEF_PARAMS6[0]);
+
+}; // anonymous namespace
+
+#endif // STD_OPTION_DEFS_H
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 945f822..4833fb5 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -27,15 +27,18 @@ if HAVE_GTEST
TESTS += libdhcp++_unittests
libdhcp___unittests_SOURCES = run_unittests.cc
+libdhcp___unittests_SOURCES += hwaddr_unittest.cc
libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
libdhcp___unittests_SOURCES += option6_addrlst_unittest.cc
libdhcp___unittests_SOURCES += option6_ia_unittest.cc
libdhcp___unittests_SOURCES += option6_iaaddr_unittest.cc
-libdhcp___unittests_SOURCES += option6_int_array_unittest.cc
-libdhcp___unittests_SOURCES += option6_int_unittest.cc
+libdhcp___unittests_SOURCES += option_int_unittest.cc
+libdhcp___unittests_SOURCES += option_int_array_unittest.cc
+libdhcp___unittests_SOURCES += option_data_types_unittest.cc
libdhcp___unittests_SOURCES += option_definition_unittest.cc
+libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_unittest.cc
libdhcp___unittests_SOURCES += pkt4_unittest.cc
libdhcp___unittests_SOURCES += pkt6_unittest.cc
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 9db4c35..de20e51 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -108,6 +108,14 @@ TEST(DuidTest, getType) {
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
@@ -174,8 +182,8 @@ TEST(ClientIdTest, operators) {
TEST(ClientIdTest, toText) {
uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
- DUID duid(data1, sizeof(data1));
- EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+ ClientId clientid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..144d62d
--- /dev/null
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(HWAddrTest, constructor) {
+
+ const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ const uint8_t htype = HTYPE_ETHER;
+
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
+ scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
+ scoped_ptr<HWAddr> hwaddr3(new HWAddr());
+
+ EXPECT_TRUE(data2 == hwaddr1->hwaddr_);
+ EXPECT_EQ(htype, hwaddr1->htype_);
+
+ EXPECT_TRUE(data2 == hwaddr2->hwaddr_);
+ EXPECT_EQ(htype, hwaddr2->htype_);
+
+ EXPECT_EQ(0, hwaddr3->hwaddr_.size());
+ EXPECT_EQ(htype, hwaddr3->htype_);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(HWAddrTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ uint8_t htype1 = HTYPE_ETHER;
+ uint8_t htype2 = HTYPE_FDDI;
+
+ scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1));
+ scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1));
+ scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1));
+ scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1));
+
+ // MAC address the same as data1 and data4, but different hardware type
+ scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2));
+
+ EXPECT_TRUE(*hw1 == *hw4);
+ EXPECT_FALSE(*hw1 == *hw2);
+ EXPECT_FALSE(*hw1 == *hw3);
+
+ EXPECT_FALSE(*hw1 != *hw4);
+ EXPECT_TRUE(*hw1 != *hw2);
+ EXPECT_TRUE(*hw1 != *hw3);
+
+ EXPECT_FALSE(*hw1 == *hw5);
+ EXPECT_FALSE(*hw4 == *hw5);
+
+ EXPECT_TRUE(*hw1 != *hw5);
+ EXPECT_TRUE(*hw4 != *hw5);
+}
+
+// Checks that toText() method produces appropriate text representation
+TEST(HWAddrTest, toText) {
+ uint8_t data[] = {0, 1, 2, 3, 4, 5};
+ uint8_t htype = 15;
+
+ HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+ EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
+
+}
+
+TEST(HWAddrTest, stringConversion) {
+
+ // Check that an empty vector returns an appropriate string
+ HWAddr hwaddr;
+ std::string result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 "), result);
+
+ // ... that a single-byte string is OK
+ hwaddr.hwaddr_.push_back(0xc3);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3"), result);
+
+ // ... and that a multi-byte string works
+ hwaddr.hwaddr_.push_back(0x7);
+ hwaddr.hwaddr_.push_back(0xa2);
+ hwaddr.hwaddr_.push_back(0xe8);
+ hwaddr.hwaddr_.push_back(0x42);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 11cbf34..d72ef9e 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -702,11 +702,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
sendPkt->setYiaddr(IOAddress("192.0.2.3"));
sendPkt->setGiaddr(IOAddress("192.0.2.4"));
- // unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present
- boost::shared_ptr<Option> msgType(new Option(Option::V4,
- static_cast<uint16_t>(DHO_DHCP_MESSAGE_TYPE)));
- msgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
- sendPkt->addOption(msgType);
+ // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+ // Workarounds (creating DHCP Message Type Option by hand) are no longer
+ // needed as setDhcpType() is called in constructor.
uint8_t sname[] = "That's just a string that will act as SNAME";
sendPkt->setSname(sname, strlen((const char*)sname));
@@ -744,7 +742,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
- EXPECT_EQ(sendPkt->getType(), rcvPkt->getType());
EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index c8a3271..a59da12 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -17,11 +17,13 @@
#include <dhcp/dhcp4.h>
#include <dhcp/dhcp6.h>
#include <dhcp/libdhcp++.h>
+#include <dhcp/option4_addrlst.h>
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int.h>
-#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -39,8 +41,7 @@ using namespace isc::util;
namespace {
class LibDhcpTest : public ::testing::Test {
public:
- LibDhcpTest() {
- }
+ LibDhcpTest() { }
/// @brief Generic factory function to create any option.
///
@@ -55,24 +56,69 @@ public:
return OptionPtr(option);
}
- /// @brief Test option option definition.
+ /// @brief Test DHCPv4 option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param code option code.
+ /// @param begin iterator pointing a begining of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing an end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ static void testStdOptionDefs4(const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type) {
+ // Use V4 universe.
+ testStdOptionDefs(Option::V4, code, begin, end, expected_type);
+ }
+
+ /// @brief Test DHCPv6 option definition.
///
/// This function tests if option definition for standard
/// option has been initialized correctly.
///
/// @param code option code.
- /// @param bug buffer to be used to create option instance.
+ /// @param begin iterator pointing a begining of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing an end of a buffer to be
+ /// used to create option instance.
/// @param expected_type type of the option created by the
/// factory function returned by the option definition.
- static void testInitOptionDefs6(const uint16_t code,
- const OptionBuffer& buf,
- const std::type_info& expected_type) {
- // Initialize stdandard options definitions. They are held
- // in the static container throughout the program.
- LibDHCP::initStdOptionDefs(Option::V6);
+ static void testStdOptionDefs6(const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type) {
+ // Use V6 universe.
+ testStdOptionDefs(Option::V6, code, begin, end, expected_type);
+ }
+private:
+
+ /// @brief Test DHCPv4 or DHCPv6 option definition.
+ ///
+ /// This function tests if option definition for standard
+ /// option has been initialized correctly.
+ ///
+ /// @param code option code.
+ /// @param begin iterator pointing a begining of a buffer to
+ /// be used to create option instance.
+ /// @param end iterator pointing an end of a buffer to be
+ /// used to create option instance.
+ /// @param expected_type type of the option created by the
+ /// factory function returned by the option definition.
+ static void testStdOptionDefs(const Option::Universe u,
+ const uint16_t code,
+ const OptionBufferConstIter begin,
+ const OptionBufferConstIter end,
+ const std::type_info& expected_type) {
// Get all option definitions, we will use them to extract
// the definition for a particular option code.
- OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
+ // We don't have to initialize option definitions here because they
+ // are initialized in the class's constructor.
+ OptionDefContainer options = LibDHCP::getOptionDefs(u);
// Get the container index #1. This one allows for searching
// option definitions using option code.
const OptionDefContainerTypeIndex& idx = options.get<1>();
@@ -80,42 +126,45 @@ public:
// For standard options we expect that the range returned
// will contain single option as their codes are unique.
OptionDefContainerTypeRange range = idx.equal_range(code);
- ASSERT_EQ(1, std::distance(range.first, range.second));
+ ASSERT_EQ(1, std::distance(range.first, range.second))
+ << "Standard option definition for the code " << code
+ << " has not been found.";
// If we have single option definition returned, the
// first iterator holds it.
OptionDefinitionPtr def = *(range.first);
// It should not happen that option definition is NULL but
// let's make sure (test should take things like that into
// account).
- ASSERT_TRUE(def);
+ ASSERT_TRUE(def) << "Option definition for the code "
+ << code << " is NULL.";
// Check that option definition is valid.
- ASSERT_NO_THROW(def->validate());
- // Get the factory function for the particular option
- // definition. We will use this factory function to
- // create option instance.
- Option::Factory* factory = NULL;
- ASSERT_NO_THROW(factory = def->getFactory());
+ ASSERT_NO_THROW(def->validate())
+ << "Option definition for the option code " << code
+ << " is invalid";
OptionPtr option;
// Create the option.
- ASSERT_NO_THROW(option = factory(Option::V6, code, buf));
+ ASSERT_NO_THROW(option = def->optionFactory(u, code, begin, end))
+ << "Option creation failed for option code " << code;
// Make sure it is not NULL.
ASSERT_TRUE(option);
// And the actual object type is the one that we expect.
// Note that for many options there are dedicated classes
// derived from Option class to represent them.
- EXPECT_TRUE(typeid(*option) == expected_type);
+ EXPECT_TRUE(typeid(*option) == expected_type)
+ << "Invalid class returned for option code " << code;
}
};
-static const uint8_t packed[] = {
- 0, 12, 0, 5, 100, 101, 102, 103, 104, // opt1 (9 bytes)
- 0, 13, 0, 3, 105, 106, 107, // opt2 (7 bytes)
- 0, 14, 0, 2, 108, 109, // opt3 (6 bytes)
- 1, 0, 0, 4, 110, 111, 112, 113, // opt4 (8 bytes)
- 1, 1, 0, 1, 114 // opt5 (5 bytes)
+// The DHCPv6 options in the wire format, used by multiple tests.
+const uint8_t v6packed[] = {
+ 0, 1, 0, 5, 100, 101, 102, 103, 104, // CLIENT_ID (9 bytes)
+ 0, 2, 0, 3, 105, 106, 107, // SERVER_ID (7 bytes)
+ 0, 14, 0, 0, // RAPID_COMMIT (0 bytes)
+ 0, 6, 0, 4, 108, 109, 110, 111, // ORO (8 bytes)
+ 0, 8, 0, 2, 112, 113 // ELAPSED_TIME (6 bytes)
};
-TEST(LibDhcpTest, optionFactory) {
+TEST_F(LibDhcpTest, optionFactory) {
OptionBuffer buf;
// Factory functions for specific options must be registered before
// they can be used to create options instances. Otherwise exception
@@ -187,7 +236,7 @@ TEST(LibDhcpTest, optionFactory) {
opt_clientid->getData().begin()));
}
-TEST(LibDhcpTest, packOptions6) {
+TEST_F(LibDhcpTest, packOptions6) {
OptionBuffer buf(512);
isc::dhcp::Option::OptionCollection opts; // list of options
@@ -196,11 +245,11 @@ TEST(LibDhcpTest, packOptions6) {
buf[i]=i+100;
}
- OptionPtr opt1(new Option(Option::V6, 12, buf.begin() + 0, buf.begin() + 5));
- OptionPtr opt2(new Option(Option::V6, 13, buf.begin() + 5, buf.begin() + 8));
- OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 10));
- OptionPtr opt4(new Option(Option::V6,256, buf.begin() + 10,buf.begin() + 14));
- OptionPtr opt5(new Option(Option::V6,257, buf.begin() + 14,buf.begin() + 15));
+ OptionPtr opt1(new Option(Option::V6, 1, buf.begin() + 0, buf.begin() + 5));
+ OptionPtr opt2(new Option(Option::V6, 2, buf.begin() + 5, buf.begin() + 8));
+ OptionPtr opt3(new Option(Option::V6, 14, buf.begin() + 8, buf.begin() + 8));
+ OptionPtr opt4(new Option(Option::V6, 6, buf.begin() + 8, buf.begin() + 12));
+ OptionPtr opt5(new Option(Option::V6, 8, buf.begin() + 12, buf.begin() + 14));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt1));
opts.insert(pair<int, OptionPtr >(opt1->getType(), opt2));
@@ -211,11 +260,11 @@ TEST(LibDhcpTest, packOptions6) {
OutputBuffer assembled(512);
EXPECT_NO_THROW(LibDHCP::packOptions6(assembled, opts));
- EXPECT_EQ(35, assembled.getLength()); // options should take 35 bytes
- EXPECT_EQ(0, memcmp(assembled.getData(), packed, 35) );
+ EXPECT_EQ(sizeof(v6packed), assembled.getLength());
+ EXPECT_EQ(0, memcmp(assembled.getData(), v6packed, sizeof(v6packed)));
}
-TEST(LibDhcpTest, unpackOptions6) {
+TEST_F(LibDhcpTest, unpackOptions6) {
// just couple of random options
// Option is used as a simple option implementation
@@ -224,67 +273,101 @@ TEST(LibDhcpTest, unpackOptions6) {
isc::dhcp::Option::OptionCollection options; // list of options
OptionBuffer buf(512);
- memcpy(&buf[0], packed, 35);
+ memcpy(&buf[0], v6packed, sizeof(v6packed));
EXPECT_NO_THROW ({
- LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin()+35), options);
+ LibDHCP::unpackOptions6(OptionBuffer(buf.begin(), buf.begin() + sizeof(v6packed)),
+ options);
});
EXPECT_EQ(options.size(), 5); // there should be 5 options
- isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
+ isc::dhcp::Option::OptionCollection::const_iterator x = options.find(1);
ASSERT_FALSE(x == options.end()); // option 1 should exist
- EXPECT_EQ(12, x->second->getType()); // this should be option 12
+ EXPECT_EQ(1, x->second->getType()); // this should be option 1
ASSERT_EQ(9, x->second->len()); // it should be of length 9
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+4, 5)); // data len=5
+ ASSERT_EQ(5, x->second->getData().size());
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 4, 5)); // data len=5
- x = options.find(13);
- ASSERT_FALSE(x == options.end()); // option 13 should exist
- EXPECT_EQ(13, x->second->getType()); // this should be option 13
+ x = options.find(2);
+ ASSERT_FALSE(x == options.end()); // option 2 should exist
+ EXPECT_EQ(2, x->second->getType()); // this should be option 2
ASSERT_EQ(7, x->second->len()); // it should be of length 7
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+13, 3)); // data len=3
+ ASSERT_EQ(3, x->second->getData().size());
+ EXPECT_EQ(0, memcmp(&x->second->getData()[0], v6packed + 13, 3)); // data len=3
x = options.find(14);
- ASSERT_FALSE(x == options.end()); // option 3 should exist
+ ASSERT_FALSE(x == options.end()); // option 14 should exist
EXPECT_EQ(14, x->second->getType()); // this should be option 14
- ASSERT_EQ(6, x->second->len()); // it should be of length 6
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+20, 2)); // data len=2
-
- x = options.find(256);
- ASSERT_FALSE(x == options.end()); // option 256 should exist
- EXPECT_EQ(256, x->second->getType()); // this should be option 256
- ASSERT_EQ(8, x->second->len()); // it should be of length 7
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+26, 4)); // data len=4
-
- x = options.find(257);
- ASSERT_FALSE(x == options.end()); // option 257 should exist
- EXPECT_EQ(257, x->second->getType()); // this should be option 257
- ASSERT_EQ(5, x->second->len()); // it should be of length 5
- EXPECT_EQ(0, memcmp(&x->second->getData()[0], packed+34, 1)); // data len=1
+ ASSERT_EQ(4, x->second->len()); // it should be of length 4
+ EXPECT_EQ(0, x->second->getData().size()); // data len = 0
+
+ x = options.find(6);
+ ASSERT_FALSE(x == options.end()); // option 6 should exist
+ EXPECT_EQ(6, x->second->getType()); // this should be option 6
+ ASSERT_EQ(8, x->second->len()); // it should be of length 8
+ // Option with code 6 is the OPTION_ORO. This option is
+ // represented by the OptionIntArray<uint16_t> class which
+ // comprises the set of uint16_t values. We need to cast the
+ // returned pointer to this type to get values stored in it.
+ boost::shared_ptr<OptionIntArray<uint16_t> > opt_oro =
+ boost::dynamic_pointer_cast<OptionIntArray<uint16_t> >(x->second);
+ // This value will be NULL if cast was unsuccessful. This is the case
+ // when returned option has different type than expected.
+ ASSERT_TRUE(opt_oro);
+ // Get set of uint16_t values.
+ std::vector<uint16_t> opts = opt_oro->getValues();
+ // Prepare the refrence data.
+ std::vector<uint16_t> expected_opts;
+ expected_opts.push_back(0x6C6D); // equivalent to: 108, 109
+ expected_opts.push_back(0x6E6F); // equivalent to 110, 111
+ ASSERT_EQ(expected_opts.size(), opts.size());
+ // Validated if option has been unpacked correctly.
+ EXPECT_TRUE(std::equal(expected_opts.begin(), expected_opts.end(),
+ opts.begin()));
+
+ x = options.find(8);
+ ASSERT_FALSE(x == options.end()); // option 8 should exist
+ EXPECT_EQ(8, x->second->getType()); // this should be option 8
+ ASSERT_EQ(6, x->second->len()); // it should be of length 9
+ // Option with code 8 is OPTION_ELAPSED_TIME. This option is
+ // represented by Option6Int<uint16_t> value that holds single
+ // uint16_t value.
+ boost::shared_ptr<OptionInt<uint16_t> > opt_elapsed_time =
+ boost::dynamic_pointer_cast<OptionInt<uint16_t> >(x->second);
+ // This value will be NULL if cast was unsuccessful. This is the case
+ // when returned option has different type than expected.
+ ASSERT_TRUE(opt_elapsed_time);
+ // Returned value should be equivalent to two byte values: 112, 113
+ EXPECT_EQ(0x7071, opt_elapsed_time->getValue());
x = options.find(0);
EXPECT_TRUE(x == options.end()); // option 0 not found
- x = options.find(1); // 1 is htons(256) on little endians. Worth checking
+ x = options.find(256); // 256 is htons(1) on little endians. Worth checking
EXPECT_TRUE(x == options.end()); // option 1 not found
- x = options.find(2);
+ x = options.find(7);
EXPECT_TRUE(x == options.end()); // option 2 not found
x = options.find(32000);
- EXPECT_TRUE(x == options.end()); // option 32000 not found
+ EXPECT_TRUE(x == options.end()); // option 32000 not found */
}
-
+/// V4 Options being used to test pack/unpack operations.
+/// These are variable length options only so as there
+/// is no restriction on the data length being carried by them.
+/// For simplicity, we assign data of the length 3 for each
+/// of them.
static uint8_t v4Opts[] = {
- 12, 3, 0, 1, 2,
- 13, 3, 10, 11, 12,
- 14, 3, 20, 21, 22,
- 254, 3, 30, 31, 32,
- 128, 3, 40, 41, 42
+ 12, 3, 0, 1, 2, // Hostname
+ 60, 3, 10, 11, 12, // Class Id
+ 14, 3, 20, 21, 22, // Merit Dump File
+ 254, 3, 30, 31, 32, // Reserved
+ 128, 3, 40, 41, 42 // Vendor specific
};
-TEST(LibDhcpTest, packOptions4) {
+TEST_F(LibDhcpTest, packOptions4) {
vector<uint8_t> payload[5];
for (int i = 0; i < 5; i++) {
@@ -295,7 +378,7 @@ TEST(LibDhcpTest, packOptions4) {
}
OptionPtr opt1(new Option(Option::V4, 12, payload[0]));
- OptionPtr opt2(new Option(Option::V4, 13, payload[1]));
+ OptionPtr opt2(new Option(Option::V4, 60, payload[1]));
OptionPtr opt3(new Option(Option::V4, 14, payload[2]));
OptionPtr opt4(new Option(Option::V4,254, payload[3]));
OptionPtr opt5(new Option(Option::V4,128, payload[4]));
@@ -316,13 +399,13 @@ TEST(LibDhcpTest, packOptions4) {
}
-TEST(LibDhcpTest, unpackOptions4) {
+TEST_F(LibDhcpTest, unpackOptions4) {
- vector<uint8_t> packed(v4Opts, v4Opts + sizeof(v4Opts));
+ vector<uint8_t> v4packed(v4Opts, v4Opts + sizeof(v4Opts));
isc::dhcp::Option::OptionCollection options; // list of options
ASSERT_NO_THROW(
- LibDHCP::unpackOptions4(packed, options);
+ LibDHCP::unpackOptions4(v4packed, options);
);
isc::dhcp::Option::OptionCollection::const_iterator x = options.find(12);
@@ -332,9 +415,9 @@ TEST(LibDhcpTest, unpackOptions4) {
EXPECT_EQ(5, x->second->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+2, 3)); // data len=3
- x = options.find(13);
- ASSERT_FALSE(x == options.end()); // option 1 should exist
- EXPECT_EQ(13, x->second->getType()); // this should be option 13
+ x = options.find(60);
+ ASSERT_FALSE(x == options.end()); // option 2 should exist
+ EXPECT_EQ(60, x->second->getType()); // this should be option 60
ASSERT_EQ(3, x->second->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->second->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->second->getData()[0], v4Opts+7, 3)); // data len=3
@@ -370,30 +453,468 @@ TEST(LibDhcpTest, unpackOptions4) {
EXPECT_TRUE(x == options.end()); // option 2 not found
}
+TEST_F(LibDhcpTest, isStandardOption4) {
+ // Get all option codes that are not occupied by standard options.
+ const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 115, 126, 127, 147, 148, 149,
+ 178, 179, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 190, 191, 192, 193, 194, 195,
+ 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 205, 206, 207, 214, 215, 216, 217, 218, 219,
+ 222, 223 };
+ const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
+
+ // Try all possible option codes.
+ for (size_t i = 0; i < 256; ++i) {
+ // Some ranges of option codes are unassigned and thus the isStandardOption
+ // should return false for them.
+ bool check_unassigned = false;
+ // Check the array of unassigned options to find out whether option code
+ // is assigned to standard option or unassigned.
+ for (size_t j = 0; j < unassigned_num; ++j) {
+ // If option code is found within the array of unassigned options
+ // we the isStandardOption function should return false.
+ if (unassigned_codes[j] == i) {
+ check_unassigned = true;
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
+ unassigned_codes[j]))
+ << "Test failed for option code " << unassigned_codes[j];
+ break;
+ }
+ }
+ // If the option code belongs to the standard option then the
+ // isStandardOption should return true.
+ if (!check_unassigned) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
+ << "Test failed for the option code " << i;
+ }
+ }
+}
+
+TEST_F(LibDhcpTest, isStandardOption6) {
+ // All option codes in the range from 0 to 78 (except 10 and 35)
+ // identify the standard options.
+ for (uint16_t code = 0; code < 79; ++code) {
+ if (code != 10 && code != 35) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+ }
+
+ // Check the option codes 10 and 35. They are unassigned.
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
+
+ // Check a range of option codes above 78. Those are option codes
+ // identifying non-standard options.
+ for (uint16_t code = 79; code < 512; ++code) {
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+}
+
+TEST_F(LibDhcpTest, stdOptionDefs4) {
+
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+ OptionBufferConstIter begin = buf.begin();
+ OptionBufferConstIter end = buf.begin();
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_MASK, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TIME_OFFSET, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TIME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NAME_SERVERS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_LOG_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_COOKIE_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_LPR_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IMPRESS_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_RESOURCE_LOCATION_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_HOST_NAME, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BOOT_SIZE, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MERIT_DUMP, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_NAME, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SWAP_SERVER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROOT_PATH, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_EXTENSIONS_PATH, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IP_FORWARDING, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NON_LOCAL_SOURCE_ROUTING, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_POLICY_FILTER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MAX_DGRAM_REASSEMBLY, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_IP_TTL, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_AGING_TIMEOUT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PATH_MTU_PLATEAU_TABLE, begin, begin + 10,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_INTERFACE_MTU, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ALL_SUBNETS_LOCAL, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_BROADCAST_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_PERFORM_MASK_DISCOVERY, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_MASK_SUPPLIER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_DISCOVERY, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ROUTER_SOLICITATION_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_STATIC_ROUTES, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TRAILER_ENCAPSULATION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ARP_CACHE_TIMEOUT, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_IEEE802_3_ENCAPSULATION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DEFAULT_TCP_TTL, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_INTERVAL, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_TCP_KEEPALIVE_GARBAGE, begin, begin + 1,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NIS_DOMAIN, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NIS_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NTP_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_ENCAPSULATED_OPTIONS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NAME_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_DD_SERVER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_NODE_TYPE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NETBIOS_SCOPE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_FONT_SERVERS, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_X_DISPLAY_MANAGER, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REQUESTED_ADDRESS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_LEASE_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_OPTION_OVERLOAD, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE_TYPE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_SERVER_IDENTIFIER, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_PARAMETER_REQUEST_LIST, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MESSAGE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_MAX_MESSAGE_SIZE, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_RENEWAL_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_REBINDING_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VENDOR_CLASS_IDENTIFIER, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_CLIENT_IDENTIFIER, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NWIP_DOMAIN_NAME, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_NWIP_SUBOPTIONS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_USER_CLASS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_FQDN, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DHCP_AGENT_OPTIONS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_AUTHENTICATE, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_CLIENT_LAST_TRANSACTION_TIME,
+ begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_ASSOCIATED_IP, begin, end,
+ typeid(Option4AddrLst));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_SUBNET_SELECTION, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_DOMAIN_SEARCH, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VIVCO_SUBOPTIONS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs4(DHO_VIVSO_SUBOPTIONS, begin, end,
+ typeid(Option));
+}
+
// Test that definitions of standard options have been initialized
// correctly.
// @todo Only limited number of option definitions are now created
// This test have to be extended once all option definitions are
// created.
-TEST(LibDhcpTest, initStdOptionDefs) {
- LibDhcpTest::testInitOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testInitOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testInitOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
- typeid(Option6IA));
- LibDhcpTest::testInitOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
- typeid(Option6IAAddr));
- LibDhcpTest::testInitOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
- typeid(Option6IntArray<uint16_t>));
- LibDhcpTest::testInitOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
- typeid(Option6Int<uint16_t>));
- LibDhcpTest::testInitOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
- typeid(Option));
- LibDhcpTest::testInitOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
- typeid(Option));
- LibDhcpTest::testInitOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
- typeid(Option6AddrLst));
+TEST_F(LibDhcpTest, stdOptionDefs6) {
+
+ // Create a buffer that holds dummy option data.
+ // It will be used to create most of the options.
+ std::vector<uint8_t> buf(48, 1);
+ OptionBufferConstIter begin = buf.begin();
+ OptionBufferConstIter end = buf.end();
+
+ // Prepare buffer holding an array of FQDNs.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+ // Initialize a vector with the FQDN data.
+ std::vector<uint8_t> fqdn_buf(data, data + sizeof(data));
+
+ // The CLIENT_FQDN holds a uint8_t value and FQDN. We have
+ // to add the uint8_t value to it and then append the buffer
+ // holding some valid FQDN.
+ std::vector<uint8_t> client_fqdn_buf(1);
+ client_fqdn_buf.insert(client_fqdn_buf.end(), fqdn_buf.begin(),
+ fqdn_buf.end());
+
+ // The actual test starts here for all supported option codes.
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, begin, end,
+ typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, begin, end,
+ typeid(Option6IAAddr));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ORO, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, begin, begin + 2,
+ typeid(OptionInt<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, begin, begin + 1,
+ typeid(OptionInt<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf.begin(),
+ fqdn_buf.end(), typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, begin, end,
+ typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+ begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf.begin(),
+ fqdn_buf.end(),
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf.begin(),
+ client_fqdn_buf.end(), typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, begin, end,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERO, begin, end,
+ typeid(OptionIntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, begin, end,
+ typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, begin, begin + 4,
+ typeid(OptionInt<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, begin, end,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, begin, end,
+ typeid(Option6AddrLst));
}
}
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index bc72242..24f101d 100644
--- a/src/lib/dhcp/tests/option6_ia_unittest.cc
+++ b/src/lib/dhcp/tests/option6_ia_unittest.cc
@@ -109,7 +109,13 @@ TEST_F(Option6IATest, basic) {
}
TEST_F(Option6IATest, simple) {
- Option6IA * ia = new Option6IA(D6O_IA_NA, 1234);
+ Option6IA* ia = new Option6IA(D6O_IA_NA, 1234);
+
+ // Check that the values are really different than what we are about
+ // to set them to.
+ EXPECT_NE(2345, ia->getT1());
+ EXPECT_NE(3456, ia->getT2());
+
ia->setT1(2345);
ia->setT2(3456);
@@ -119,6 +125,9 @@ TEST_F(Option6IATest, simple) {
EXPECT_EQ(2345, ia->getT1());
EXPECT_EQ(3456, ia->getT2());
+ ia->setIAID(890);
+ EXPECT_EQ(890, ia->getIAID());
+
EXPECT_NO_THROW(
delete ia;
);
@@ -206,7 +215,7 @@ TEST_F(Option6IATest, suboptions_unpack) {
Option6IA* ia = 0;
EXPECT_NO_THROW({
- ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
+ ia = new Option6IA(D6O_IA_NA, buf_.begin() + 4, buf_.begin() + sizeof(expected));
});
ASSERT_TRUE(ia);
diff --git a/src/lib/dhcp/tests/option6_int_array_unittest.cc b/src/lib/dhcp/tests/option6_int_array_unittest.cc
deleted file mode 100644
index 3a8640a..0000000
--- a/src/lib/dhcp/tests/option6_int_array_unittest.cc
+++ /dev/null
@@ -1,421 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-
-#include <dhcp/dhcp6.h>
-#include <dhcp/option.h>
-#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int_array.h>
-#include <util/buffer.h>
-
-#include <boost/pointer_cast.hpp>
-#include <gtest/gtest.h>
-
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::asiolink;
-using namespace isc::util;
-
-namespace {
-
-/// @brief Option6IntArray test class.
-class Option6IntArrayTest : public ::testing::Test {
-public:
- /// @brief Constructor.
- ///
- /// Initializes the option buffer with some data.
- Option6IntArrayTest(): buf_(255), out_buf_(255) {
- for (int i = 0; i < 255; i++) {
- buf_[i] = 255 - i;
- }
- }
-
- /// @brief Test parsing buffer into array of int8_t or uint8_t values.
- ///
- /// @warning this function does not perform type check. Make
- /// sure that only int8_t or uint8_t type is used.
- ///
- /// @tparam T int8_t or uint8_t.
- template<typename T>
- void bufferToIntTest8() {
- // Create option that conveys array of multiple uint8_t or int8_t values.
- // In fact there is no need to use this template class for array
- // of uint8_t values because Option class is sufficient - it
- // returns the buffer which is actually the array of uint8_t.
- // However, since we allow using uint8_t types with this template
- // class we have to test it here.
- boost::shared_ptr<Option6IntArray<T> > opt;
- const int opt_len = 10;
- const uint16_t opt_code = 80;
-
- // Constructor throws exception if provided buffer is empty.
- EXPECT_THROW(
- Option6IntArray<T>(opt_code, buf_.begin(), buf_.begin()),
- isc::OutOfRange
- );
-
- // Provided buffer is not empty so it should not throw exception.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<
- Option6IntArray<T> >(new Option6IntArray<T>(opt_code, buf_.begin(),
- buf_.begin() + opt_len))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- // Option should return the collection of int8_t or uint8_t values that
- // we can match with the buffer we used to create the option.
- std::vector<T> values = opt->getValues();
- // We need to copy values from the buffer to apply sign if signed
- // type is used.
- std::vector<T> reference_values;
- for (int i = 0; i < opt_len; ++i) {
- // Values have been read from the buffer in network
- // byte order. We put them back in the same order here.
- reference_values.push_back(static_cast<T>(buf_[i]));
- }
-
- // Compare the values against the reference buffer.
- ASSERT_EQ(opt_len, values.size());
- EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.begin()
- + opt_len, values.begin()));
-
- // test for pack()
- opt->pack(out_buf_);
-
- // Data length is 10 bytes.
- EXPECT_EQ(10, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(opt_code, opt->getType());
- // The total length is 10 bytes for data and 4 bytes for header.
- ASSERT_EQ(14, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(opt_code, out.readUint16());
- // if option length is correct
- EXPECT_EQ(10, out.readUint16());
- // if data is correct
- std::vector<uint8_t> out_data;
- ASSERT_NO_THROW(out.readVector(out_data, opt_len));
- ASSERT_EQ(opt_len, out_data.size());
- EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
- }
-
- /// @brief Test parsing buffer into array of int16_t or uint16_t values.
- ///
- /// @warning this function does not perform type check. Make
- /// sure that only int16_t or uint16_t type is used.
- ///
- /// @tparam T int16_t or uint16_t.
- template<typename T>
- void bufferToIntTest16() {
- // Create option that conveys array of multiple uint16_t or int16_t values.
- boost::shared_ptr<Option6IntArray<T> > opt;
- const int opt_len = 20;
- const uint16_t opt_code = 81;
-
- // Constructor throws exception if provided buffer is empty.
- EXPECT_THROW(
- Option6IntArray<T>(opt_code, buf_.begin(), buf_.begin()),
- isc::OutOfRange
- );
-
- // Constructor throws exception if provided buffer's length is not
- // multiple of 2-bytes.
- EXPECT_THROW(
- Option6IntArray<T>(opt_code, buf_.begin(), buf_.begin() + 5),
- isc::OutOfRange
- );
-
- // Now the buffer length is correct.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<
- Option6IntArray<T> >(new Option6IntArray<T>(opt_code, buf_.begin(),
- buf_.begin() + opt_len))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- // Option should return vector of uint16_t values which should be
- // constructed from the buffer we provided.
- std::vector<T> values = opt->getValues();
- ASSERT_EQ(opt_len, values.size() * sizeof(T));
- // Create reference values from the buffer so as we can
- // simply compare two vectors.
- std::vector<T> reference_values;
- for (int i = 0; i < opt_len; i += 2) {
- reference_values.push_back((buf_[i] << 8) |
- buf_[i + 1]);
- }
- EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
- values.begin()));
-
- // Test for pack()
- opt->pack(out_buf_);
-
- // Data length is 20 bytes.
- EXPECT_EQ(20, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(opt_code, opt->getType());
- // The total length is 20 bytes for data and 4 bytes for header.
- ASSERT_EQ(24, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(opt_code, out.readUint16());
- // if option length is correct
- EXPECT_EQ(20, out.readUint16());
- // if data is correct
- std::vector<uint8_t> out_data;
- ASSERT_NO_THROW(out.readVector(out_data, opt_len));
- ASSERT_EQ(opt_len, out_data.size());
- EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
- }
-
- /// @brief Test parsing buffer into array of int32_t or uint32_t values.
- ///
- /// @warning this function does not perform type check. Make
- /// sure that only int32_t or uint32_t type is used.
- ///
- /// @tparam T int32_t or uint32_t.
- template<typename T>
- void bufferToIntTest32() {
- // Create option that conveys array of multiple uint16_t values.
- boost::shared_ptr<Option6IntArray<T> > opt;
- const int opt_len = 40;
- const uint16_t opt_code = 82;
-
- // Constructor throws exception if provided buffer is empty.
- EXPECT_THROW(
- Option6IntArray<T>(opt_code, buf_.begin(), buf_.begin()),
- isc::OutOfRange
- );
-
- // Constructor throws exception if provided buffer's length is not
- // multiple of 4-bytes.
- EXPECT_THROW(
- Option6IntArray<T>(opt_code, buf_.begin(), buf_.begin() + 9),
- isc::OutOfRange
- );
-
- // Now the buffer length is correct.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<
- Option6IntArray<T> >(new Option6IntArray<T>(opt_code, buf_.begin(),
- buf_.begin() + opt_len))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- // Option should return vector of uint32_t values which should be
- // constructed from the buffer we provided.
- std::vector<T> values = opt->getValues();
- ASSERT_EQ(opt_len, values.size() * sizeof(T));
- // Create reference values from the buffer so as we can
- // simply compare two vectors.
- std::vector<T> reference_values;
- for (int i = 0; i < opt_len; i += 4) {
- reference_values.push_back((buf_[i] << 24) |
- (buf_[i + 1] << 16 & 0x00FF0000) |
- (buf_[i + 2] << 8 & 0xFF00) |
- (buf_[i + 3] & 0xFF));
- }
- EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
- values.begin()));
-
- // Test for pack()
- opt->pack(out_buf_);
-
- // Data length is 40 bytes.
- EXPECT_EQ(40, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(opt_code, opt->getType());
- // The total length is 40 bytes for data and 4 bytes for header.
- ASSERT_EQ(44, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(opt_code, out.readUint16());
- // if option length is correct
- EXPECT_EQ(40, out.readUint16());
- // if data is correct
- std::vector<uint8_t> out_data;
- ASSERT_NO_THROW(out.readVector(out_data, opt_len));
- ASSERT_EQ(opt_len, out_data.size());
- EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
- }
-
-
- OptionBuffer buf_; ///< Option buffer
- OutputBuffer out_buf_; ///< Output buffer
-};
-
-/// @todo: below, there is a bunch of tests for options that
-/// convey unsigned values. We should maybe extend these tests for
-/// signed types too.
-
-TEST_F(Option6IntArrayTest, useInvalidType) {
- const uint16_t opt_code = 80;
- EXPECT_THROW(
- boost::scoped_ptr<
- Option6IntArray<bool> >(new Option6IntArray<bool>(opt_code, OptionBuffer(5))),
- InvalidDataType
- );
-
- EXPECT_THROW(
- boost::scoped_ptr<
- Option6IntArray<int64_t> >(new Option6IntArray<int64_t>(opt_code,
- OptionBuffer(10))),
- InvalidDataType
- );
-
-}
-
-TEST_F(Option6IntArrayTest, bufferToUint8) {
- bufferToIntTest8<uint8_t>();
-}
-
-TEST_F(Option6IntArrayTest, bufferToInt8) {
- bufferToIntTest8<int8_t>();
-}
-
-TEST_F(Option6IntArrayTest, bufferToUint16) {
- bufferToIntTest16<uint16_t>();
-}
-
-TEST_F(Option6IntArrayTest, bufferToInt16) {
- bufferToIntTest16<int16_t>();
-}
-
-TEST_F(Option6IntArrayTest, bufferToUint32) {
- bufferToIntTest32<uint32_t>();
-}
-
-TEST_F(Option6IntArrayTest, bufferToInt32) {
- bufferToIntTest32<int32_t>();
-}
-
-TEST_F(Option6IntArrayTest, setValuesUint8) {
- const uint16_t opt_code = 100;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<uint8_t> > opt(new Option6IntArray<uint8_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<uint8_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<uint8_t>::max() - i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<uint8_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-TEST_F(Option6IntArrayTest, setValuesInt8) {
- const uint16_t opt_code = 100;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<int8_t> > opt(new Option6IntArray<int8_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<int8_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<int8_t>::min() + i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<int8_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-TEST_F(Option6IntArrayTest, setValuesUint16) {
- const uint16_t opt_code = 101;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<uint16_t> > opt(new Option6IntArray<uint16_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<uint16_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<uint16_t>::max() - i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<uint16_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-TEST_F(Option6IntArrayTest, setValuesInt16) {
- const uint16_t opt_code = 101;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<int16_t> > opt(new Option6IntArray<int16_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<int16_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<int16_t>::min() + i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<int16_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-TEST_F(Option6IntArrayTest, setValuesUint32) {
- const uint32_t opt_code = 101;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<uint32_t> > opt(new Option6IntArray<uint32_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<uint32_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<uint32_t>::max() - i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<uint32_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-TEST_F(Option6IntArrayTest, setValuesInt32) {
- const uint32_t opt_code = 101;
- // Create option with empty vector of values.
- boost::shared_ptr<Option6IntArray<int32_t> > opt(new Option6IntArray<int32_t>(opt_code));
- // Initialize vector with some data and pass to the option.
- std::vector<int32_t> values;
- for (int i = 0; i < 10; ++i) {
- values.push_back(numeric_limits<int32_t>::min() + i);
- }
- opt->setValues(values);
-
- // Check if universe, option type and data was set correctly.
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(opt_code, opt->getType());
- std::vector<int32_t> returned_values = opt->getValues();
- EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
-}
-
-
-} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option6_int_unittest.cc b/src/lib/dhcp/tests/option6_int_unittest.cc
deleted file mode 100644
index c9eac9c..0000000
--- a/src/lib/dhcp/tests/option6_int_unittest.cc
+++ /dev/null
@@ -1,414 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <config.h>
-
-#include <dhcp/dhcp6.h>
-#include <dhcp/option.h>
-#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int.h>
-#include <util/buffer.h>
-
-#include <boost/pointer_cast.hpp>
-#include <gtest/gtest.h>
-
-using namespace std;
-using namespace isc;
-using namespace isc::dhcp;
-using namespace isc::asiolink;
-using namespace isc::util;
-
-namespace {
-
-/// @brief Option6Int test class.
-class Option6IntTest : public ::testing::Test {
-public:
- /// @brief Constructor.
- ///
- /// Initializes the option buffer with some data.
- Option6IntTest(): buf_(255), out_buf_(255) {
- for (int i = 0; i < 255; i++) {
- buf_[i] = 255 - i;
- }
- }
-
- /// @brief Basic test for int8 and uint8 types.
- ///
- /// @note this function does not perform type check. Make
- /// sure that only int8_t or uint8_t type is used.
- ///
- /// @tparam T int8_t or uint8_t.
- template<typename T>
- void basicTest8() {
- // Create option that conveys single 8 bit integer value.
- boost::shared_ptr<Option6Int<T> > opt;
- // Initialize buffer with this value.
- buf_[0] = 0xa1;
- // Constructor may throw in case provided buffer is too short.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<Option6Int<T> >(new Option6Int<T>(D6O_PREFERENCE,
- buf_.begin(),
- buf_.end()))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // Option should return the same value that we initialized the first
- // byte of the buffer with.
- EXPECT_EQ(static_cast<T>(0xa1), opt->getValue());
-
- // test for pack()
- opt->pack(out_buf_);
-
- // Data length is 1 byte.
- EXPECT_EQ(1, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // The total length is 1 byte for data and 4 bytes for header.
- EXPECT_EQ(5, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(D6O_PREFERENCE, out.readUint16());
- // if option length is correct
- EXPECT_EQ(1, out.readUint16());
- // if data is correct
- EXPECT_EQ(0xa1, out.readUint8() );
- }
-
- /// @brief Basic test for int16 and uint16 types.
- ///
- /// @note this function does not perform type check. Make
- /// sure that only int16_t or uint16_t type is used.
- ///
- /// @tparam T int16_t or uint16_t.
- template<typename T>
- void basicTest16() {
- // Create option that conveys single 16-bit integer value.
- boost::shared_ptr<Option6Int<T> > opt;
- // Initialize buffer with uint16_t value.
- buf_[0] = 0xa1;
- buf_[1] = 0xa2;
- // Constructor may throw in case provided buffer is too short.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<Option6Int<T> >(new Option6Int<T>(D6O_ELAPSED_TIME,
- buf_.begin(),
- buf_.end()))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // Option should return the value equal to the contents of first
- // and second byte of the buffer.
- EXPECT_EQ(static_cast<T>(0xa1a2), opt->getValue());
-
- // Test for pack()
- opt->pack(out_buf_);
-
- // Data length is 2 bytes.
- EXPECT_EQ(2, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // The total length is 2 byte for data and 4 bytes for header.
- EXPECT_EQ(6, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(D6O_ELAPSED_TIME, out.readUint16());
- // if option length is correct
- EXPECT_EQ(2, out.readUint16());
- // if data is correct
- EXPECT_EQ(0xa1a2, out.readUint16() );
- }
-
- /// @brief Basic test for int32 and uint32 types.
- ///
- /// @note this function does not perform type check. Make
- /// sure that only int32_t or uint32_t type is used.
- ///
- /// @tparam T int32_t or uint32_t.
- template<typename T>
- void basicTest32() {
- // Create option that conveys single 32-bit integer value.
- boost::shared_ptr<Option6Int<T> > opt;
- // Initialize buffer with 32-bit integer value.
- buf_[0] = 0xa1;
- buf_[1] = 0xa2;
- buf_[2] = 0xa3;
- buf_[3] = 0xa4;
- // Constructor may throw in case provided buffer is too short.
- ASSERT_NO_THROW(
- opt = boost::shared_ptr<Option6Int<T> >(new Option6Int<T>(D6O_CLT_TIME,
- buf_.begin(),
- buf_.end()))
- );
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // Option should return the value equal to the value made of
- // first 4 bytes of the buffer.
- EXPECT_EQ(static_cast<T>(0xa1a2a3a4), opt->getValue());
-
- // Test for pack()
- opt->pack(out_buf_);
-
- // Data length is 4 bytes.
- EXPECT_EQ(4, opt->len() - opt->getHeaderLen());
- EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // The total length is 4 bytes for data and 4 bytes for header.
- EXPECT_EQ(8, out_buf_.getLength());
-
- // Check if pack worked properly:
- InputBuffer out(out_buf_.getData(), out_buf_.getLength());
- // if option type is correct
- EXPECT_EQ(D6O_CLT_TIME, out.readUint16());
- // if option length is correct
- EXPECT_EQ(4, out.readUint16());
- // if data is correct
- EXPECT_EQ(0xa1a2a3a4, out.readUint32());
- }
-
- OptionBuffer buf_; ///< Option buffer
- OutputBuffer out_buf_; ///< Output buffer
-};
-
-/// @todo: below, there is a bunch of tests for options that
-/// convey unsigned value. We should maybe extend these tests for
-/// signed types too.
-
-TEST_F(Option6IntTest, useInvalidType) {
- EXPECT_THROW(
- boost::scoped_ptr<Option6Int<bool> >(new Option6Int<bool>(D6O_ELAPSED_TIME, 10)),
- InvalidDataType
- );
-
- EXPECT_THROW(
- boost::scoped_ptr<Option6Int<int64_t> >(new Option6Int<int64_t>(D6O_ELAPSED_TIME, 10)),
- InvalidDataType
- );
-
-}
-
-TEST_F(Option6IntTest, basicUint8) {
- basicTest8<uint8_t>();
-}
-
-TEST_F(Option6IntTest, basicUint16) {
- basicTest16<uint16_t>();
-}
-
-TEST_F(Option6IntTest, basicUint32) {
- basicTest32<uint32_t>();
-}
-
-TEST_F(Option6IntTest, basicInt8) {
- basicTest8<int8_t>();
-}
-
-TEST_F(Option6IntTest, basicInt16) {
- basicTest16<int16_t>();
-}
-
-TEST_F(Option6IntTest, basicInt32) {
- basicTest32<int32_t>();
-}
-
-TEST_F(Option6IntTest, setValueUint8) {
- boost::shared_ptr<Option6Int<uint8_t> > opt(new Option6Int<uint8_t>(D6O_PREFERENCE, 123));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(123, opt->getValue());
- // Override the value.
- opt->setValue(111);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(111, opt->getValue());
-}
-
-TEST_F(Option6IntTest, setValueInt8) {
- boost::shared_ptr<Option6Int<int8_t> > opt(new Option6Int<int8_t>(D6O_PREFERENCE, -123));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(-123, opt->getValue());
- // Override the value.
- opt->setValue(-111);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_PREFERENCE, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(-111, opt->getValue());
-}
-
-
-TEST_F(Option6IntTest, setValueUint16) {
- boost::shared_ptr<Option6Int<uint16_t> > opt(new Option6Int<uint16_t>(D6O_ELAPSED_TIME, 123));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(123, opt->getValue());
- // Override the value.
- opt->setValue(0x0102);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(0x0102, opt->getValue());
-}
-
-TEST_F(Option6IntTest, setValueInt16) {
- boost::shared_ptr<Option6Int<int16_t> > opt(new Option6Int<int16_t>(D6O_ELAPSED_TIME, -16500));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(-16500, opt->getValue());
- // Override the value.
- opt->setValue(-20100);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(-20100, opt->getValue());
-}
-
-TEST_F(Option6IntTest, setValueUint32) {
- boost::shared_ptr<Option6Int<uint32_t> > opt(new Option6Int<uint32_t>(D6O_CLT_TIME, 123));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(123, opt->getValue());
- // Override the value.
- opt->setValue(0x01020304);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(0x01020304, opt->getValue());
-}
-
-TEST_F(Option6IntTest, setValueint32) {
- boost::shared_ptr<Option6Int<int32_t> > opt(new Option6Int<int32_t>(D6O_CLT_TIME, -120100));
- // Check if constructor intitialized the option value correctly.
- EXPECT_EQ(-120100, opt->getValue());
- // Override the value.
- opt->setValue(-125000);
-
- EXPECT_EQ(Option::V6, opt->getUniverse());
- EXPECT_EQ(D6O_CLT_TIME, opt->getType());
- // Check if the value has been overriden.
- EXPECT_EQ(-125000, opt->getValue());
-}
-
-TEST_F(Option6IntTest, packSuboptions) {
- // option code is really uint16_t, but using uint8_t
- // for easier conversion to uint8_t array.
- uint8_t opt_code = 80;
-
- boost::shared_ptr<Option6Int<uint32_t> > opt(new Option6Int<uint32_t>(opt_code, 0x01020304));
- OptionPtr sub1(new Option(Option::V6, 0xcafe));
-
- boost::shared_ptr<Option6IAAddr> addr1(
- new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000));
-
- opt->addOption(sub1);
- opt->addOption(addr1);
-
- ASSERT_EQ(28, addr1->len());
- ASSERT_EQ(4, sub1->len());
- ASSERT_EQ(40, opt->len());
-
- uint8_t expected[] = {
- 0, opt_code, // type
- 0, 36, // length
- 0x01, 0x02, 0x03, 0x04, // uint32_t value
-
- // iaaddr suboption
- D6O_IAADDR / 256, D6O_IAADDR % 256, // type
- 0, 24, // len
- 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
- 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
- 0, 0, 0x50, 0, // preferred-lifetime
- 0, 0, 0x70, 0, // valid-lifetime
-
- // suboption
- 0xca, 0xfe, // type
- 0, 0 // len
- };
-
- // Create on-wire format of option and suboptions.
- opt->pack(out_buf_);
- // Compare the on-wire data with the reference buffer.
- ASSERT_EQ(40, out_buf_.getLength());
- EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, 40));
-}
-
-
-TEST_F(Option6IntTest, unpackSuboptions) {
- // option code is really uint16_t, but using uint8_t
- // for easier conversion to uint8_t array.
- const uint8_t opt_code = 80;
- // Prepare reference data.
- uint8_t expected[] = {
- 0, opt_code, // type
- 0, 34, // length
- 0x01, 0x02, // uint16_t value
-
- // iaaddr suboption
- D6O_IAADDR / 256, D6O_IAADDR % 256, // type
- 0, 24, // len
- 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
- 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
- 0, 0, 0x50, 0, // preferred-lifetime
- 0, 0, 0x70, 0, // valid-lifetime
-
- // suboption
- 0xca, 0xfe, // type
- 0, 0 // len
- };
- ASSERT_EQ(38, sizeof(expected));
-
- memcpy(&buf_[0], expected, sizeof(expected));
-
- boost::shared_ptr<Option6Int<uint16_t> > opt;
- EXPECT_NO_THROW(
- opt = boost::shared_ptr<
- Option6Int<uint16_t> >(new Option6Int<uint16_t>(opt_code, buf_.begin() + 4,
- buf_.begin() + sizeof(expected)));
- );
- ASSERT_TRUE(opt);
-
- EXPECT_EQ(opt_code, opt->getType());
- EXPECT_EQ(0x0102, opt->getValue());
-
- // Checks for address option
- OptionPtr subopt = opt->getOption(D6O_IAADDR);
- ASSERT_TRUE(subopt);
- boost::shared_ptr<Option6IAAddr> addr(boost::dynamic_pointer_cast<Option6IAAddr>(subopt));
- ASSERT_TRUE(addr);
-
- EXPECT_EQ(D6O_IAADDR, addr->getType());
- EXPECT_EQ(28, addr->len());
- EXPECT_EQ(0x5000, addr->getPreferred());
- EXPECT_EQ(0x7000, addr->getValid());
- EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
-
- // Checks for dummy option
- subopt = opt->getOption(0xcafe);
- ASSERT_TRUE(subopt); // should be non-NULL
-
- EXPECT_EQ(0xcafe, subopt->getType());
- EXPECT_EQ(4, subopt->len());
- // There should be no data at all
- EXPECT_EQ(0, subopt->getData().size());
-
- // Try to get non-existent option.
- subopt = opt->getOption(1);
- // Expecting NULL which means that option does not exist.
- ASSERT_FALSE(subopt);
-}
-
-} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
new file mode 100644
index 0000000..a34852b
--- /dev/null
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -0,0 +1,1407 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/option_custom.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ OptionCustomTest() { }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The purpose of this test is to check that parameters passed to
+// a custom option's constructor are used to initialize class
+// members.
+TEST_F(OptionCustomTest, constructor) {
+ // Create option definition for a DHCPv6 option.
+ OptionDefinition opt_def1("OPTION_FOO", 1000, "boolean", true);
+
+ // Initialize some dummy buffer that holds single boolean value.
+ OptionBuffer buf;
+ buf.push_back(1);
+
+ // Create DHCPv6 option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def1, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // Check if constructor initialized the universe and type correctly.
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
+
+ // Do another round of testing for DHCPv4 option.
+ OptionDefinition opt_def2("OPTION_FOO", 232, "boolean");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def2, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V4, option->getUniverse());
+ EXPECT_EQ(232, option->getType());
+
+ // Try to create an option using 'empty data' constructor
+ OptionDefinition opt_def3("OPTION_FOO", 1000, "uint32");
+
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def3, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ EXPECT_EQ(Option::V6, option->getUniverse());
+ EXPECT_EQ(1000, option->getType());
+}
+
+// The purpose of this test is to verify that 'empty' option definition can
+// be used to create an instance of custom option.
+TEST_F(OptionCustomTest, emptyData) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "empty");
+
+ OptionBuffer buf;
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // Option is 'empty' so no data fields are expected.
+ EXPECT_EQ(0, option->getDataFieldsNum());
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a binary value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, binaryData) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "binary");
+
+ // Create a buffer holding some binary data. This data will be
+ // used as reference when we read back the data from a created
+ // option.
+ OptionBuffer buf_in(14);
+ for (int i = 0; i < 14; ++i) {
+ buf_in[i] = i;
+ }
+ // Use scoped pointer because it allows to declare the option
+ // in the function scope and initialize it under ASSERT.
+ boost::scoped_ptr<OptionCustom> option;
+ // Custom option may throw exception if the provided buffer is
+ // malformed.
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // The custom option should hold just one buffer that can be
+ // accessed using index 0.
+ OptionBuffer buf_out;
+ ASSERT_NO_THROW(buf_out = option->readBinary(0));
+
+ // Read buffer must match exactly with the buffer used to
+ // create option instance.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ // Check that option with "no data" is rejected.
+ buf_in.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+ buf_in.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that an option definition comprising
+// a single boolean value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+ OptionBuffer buf;
+ // Push back the value that represents 'false'.
+ buf.push_back(0);
+ // Push back the 'true' value. Note that this value should
+ // be ignored by custom option because it holds single boolean
+ // value (according to option definition).
+ buf.push_back(1);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize the value to true because we want to make sure
+ // that it is modified to 'false' by readBoolean below.
+ bool value = true;
+
+ // Read the boolean value from only one available buffer indexed
+ // with 0. It is expected to be 'false'.
+ ASSERT_NO_THROW(value = option->readBoolean(0));
+ EXPECT_FALSE(value);
+
+ // Check that the option with "no data" is rejected.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the data from a buffer
+// can be read as FQDN.
+TEST_F(OptionCustomTest, fqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ // Check that the option with truncated data can't be created.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6,
+ buf.begin(), buf.begin() + 4)),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 16-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int16Data) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "int16");
+
+ OptionBuffer buf;
+ // Store signed integer value in the input buffer.
+ writeInt<int16_t>(-234, buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize value to 0 explicitely to make sure that is
+ // modified by readInteger function to expected -234.
+ int16_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<int16_t>(0));
+ EXPECT_EQ(-234, value);
+
+ // Check that the option is not created when a buffer is
+ // too short (1 byte instead of 2 bytes).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 1)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// 32-bit signed integer value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, int32Data) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "int32");
+
+ OptionBuffer buf;
+ writeInt<int32_t>(-234, buf);
+ writeInt<int32_t>(100, buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Initialize value to 0 explicitely to make sure that is
+ // modified by readInteger function to expected -234.
+ int32_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<int32_t>(0));
+ EXPECT_EQ(-234, value);
+
+ // Check that the option is not created when a buffer is
+ // too short (3 bytes instead of 4 bytes).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv4 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address");
+
+ // Create input buffer.
+ OptionBuffer buf;
+ writeAddress(IOAddress("192.168.100.50"), buf);
+
+ // Create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ IOAddress address("127.0.0.1");
+ // Read IPv4 address from using index 0.
+ ASSERT_NO_THROW(address = option->readAddress(0));
+
+ EXPECT_EQ("192.168.100.50", address.toText());
+
+ // Check that option is not created if the provided buffer is
+ // too short (use 3 bytes instead of 4).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// single IPv6 addres can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+ // Initialize input buffer.
+ OptionBuffer buf;
+ writeAddress(IOAddress("2001:db8:1::100"), buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should comprise exactly one buffer that represents
+ // IPv6 address.
+ IOAddress address("::1");
+ // Read an address from buffer #0.
+ ASSERT_NO_THROW(address = option->readAddress(0));
+
+ EXPECT_EQ("2001:db8:1::100", address.toText());
+
+ // Check that option is not created if the provided buffer is
+ // too short (use 15 bytes instead of 16).
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+ buf.begin() + 15)),
+ isc::OutOfRange
+ );
+}
+
+
+// The purpose of this test is to verify that the option definition comprising
+// string value can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, stringData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+ // Create an input buffer holding some string value.
+ OptionBuffer buf;
+ writeString("hello world!", buf);
+
+ // Create custom option using input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have just one data field.
+ ASSERT_EQ(1, option->getDataFieldsNum());
+
+ // Custom option should now comprise single string value that
+ // can be accessed using index 0.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString(0));
+
+ EXPECT_EQ("hello world!", value);
+
+ // Check that option will not be created if empty buffer is provided.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of boolean values can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, booleanDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+ // Create a buffer with 5 values that represent array of
+ // booleans.
+ OptionBuffer buf(5);
+ buf[0] = 1; // true
+ buf[1] = 0; // false
+ buf[2] = 0; // false
+ buf[3] = 1; // true
+ buf[4] = 1; // true
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 5 data fields.
+ ASSERT_EQ(5, option->getDataFieldsNum());
+
+ // Read values from custom option using indexes 0..4 and
+ // check that they are valid.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+
+ bool value2 = true;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_FALSE(value2);
+
+ bool value3 = false;
+ ASSERT_NO_THROW(value3 = option->readBoolean(3));
+ EXPECT_TRUE(value3);
+
+ bool value4 = false;
+ ASSERT_NO_THROW(value4 = option->readBoolean(4));
+ EXPECT_TRUE(value4);
+
+ // Check that empty buffer can't be used to create option holding
+ // array of boolean values.
+ buf.clear();
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of 32-bit signed integer values can be used to create an instance
+// of custom option.
+TEST_F(OptionCustomTest, uint32DataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint32", true);
+
+ // Create an input buffer that holds 4 uint32 values that
+ // represent an array.
+ std::vector<uint32_t> values;
+ values.push_back(71234);
+ values.push_back(12234);
+ values.push_back(54362);
+ values.push_back(1234);
+
+ // Store these values in a buffer.
+ OptionBuffer buf;
+ for (int i = 0; i < values.size(); ++i) {
+ writeInt<uint32_t>(values[i], buf);
+ }
+ // Create custom option using the input buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ // Note that we just use a part of the whole buffer here: 13 bytes. We want to
+ // check that buffer length which is non-divisible by 4 (size of uint32_t) is
+ // accepted and only 3 (instead of 4) elements will be stored in a custom option.
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 13));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Expect only 3 values.
+ for (int i = 0; i < 3; ++i) {
+ uint32_t value = 0;
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>(i));
+ EXPECT_EQ(values[i], value);
+ }
+
+ // Check that too short buffer can't be used to create the option.
+ // Using buffer having length of 3 bytes. The length of 4 bytes is
+ // a minimal length to create the option with single uint32_t value.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.begin() + 3)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv4 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ OptionBuffer buf;
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+
+ // Check that it is ok if buffer length is not a multiple of IPv4
+ // address length. Resize it by two bytes.
+ buf.resize(buf.size() + 2);
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+
+ // Check that option is not created when the provided buffer
+ // is too short. At least a buffer length of 4 bytes is needed.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(),
+ buf.begin() + 2)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// an array of IPv6 addresses can be used to create an instance of custom option.
+TEST_F(OptionCustomTest, ipv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("2001:db8:1::3"));
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::3"));
+
+ // Store the collection of IPv6 addresses into the buffer.
+ OptionBuffer buf;
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv6 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("fe80::4");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+
+ // Check that it is ok if buffer length is not a multiple of IPv6
+ // address length. Resize it by two bytes.
+ buf.resize(buf.size() + 2);
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+
+ // Check that option is not created when the provided buffer
+ // is too short. At least a buffer length of 16 bytes is needed.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(),
+ buf.begin() + 15)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that the option comprising
+// an array of FQDN values can be created from a buffer which holds
+// multiple FQDN values encoded as described in the RFC1035, section
+// 3.1
+TEST_F(OptionCustomTest, fqdnDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn", true);
+
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Create a buffer that holds two FQDNs.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Create an option from using a buffer.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that two FQDN values have been extracted
+ // from a buffer.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Validate both values.
+ std::string domain0 = option->readFqdn(0);
+ EXPECT_EQ("mydomain.example.com.", domain0);
+
+ std::string domain1 = option->readFqdn(1);
+ EXPECT_EQ("example.com.", domain1);
+}
+
+// The purpose of this test is to verify that the option definition comprising
+// a record of various data fields can be used to create an instance of
+// custom option.
+TEST_F(OptionCustomTest, recordData) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ const char fqdn_data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0,
+ };
+
+ OptionBuffer buf;
+ // Initialize field 0 to 8712.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to 'true'
+ buf.push_back(static_cast<unsigned short>(1));
+ // Initialize field 2 to 'mydomain.example.com'.
+ buf.insert(buf.end(), fqdn_data, fqdn_data + sizeof(fqdn_data));
+ // Initialize field 3 to IPv4 address.
+ writeAddress(IOAddress("192.168.0.1"), buf);
+ // Initialize field 4 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize field 5 to string value.
+ writeString("ABCD", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 6 data fields.
+ ASSERT_EQ(6, option->getDataFieldsNum());
+
+ // Verify value in the field 0.
+ uint16_t value0 = 0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(8712, value0);
+
+ // Verify value in the field 1.
+ bool value1 = false;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+
+ // Verify value in the field 2.
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
+
+ // Verify value in the field 3.
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+
+ // Verify value in the field 4.
+ IOAddress value4("::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::1", value4.toText());
+
+ // Verify value in the field 5.
+ std::string value5;
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ("ABCD", value5);
+}
+
+// The purpose of this test is to verify that truncated buffer
+// can't be used to create an option being a record of value of
+// different types.
+TEST_F(OptionCustomTest, recordDataTruncated) {
+ // Create the definition of an option which comprises
+ // a record of fields of different types.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ OptionBuffer buf;
+ // Initialize field 0.
+ writeInt<uint16_t>(8712, buf);
+ // Initialize field 1 to IPv6 address.
+ writeAddress(IOAddress("2001:db8:1::1"), buf);
+ // Initialize field 2 to string value.
+ writeString("ABCD", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+
+ // Constructor should not throw exception here because the length of the
+ // buffer meets the minimum length. The first 19 bytes hold data for
+ // all option fields: uint16, IPv4 address and first letter of string.
+ // Note that string will be truncated but this is acceptable because
+ // constructor have no way to determine the length of the original string.
+ EXPECT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 19));
+ );
+
+ // Reduce the buffer length by one byte should cause the constructor
+ // to fail. This is because 18 bytes can only hold first two data fields:
+ // 2 bytes of uint16_t value and IPv6 address. Option definitions specifies
+ // 3 data fields for this option but the length of the data is insufficient
+ // to initialize 3 data field.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 18)),
+ isc::OutOfRange
+ );
+
+ // Try to further reduce the length of the buffer to make it insufficient
+ // to even initialize the second data field.
+ EXPECT_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.begin() + 17)),
+ isc::OutOfRange
+ );
+}
+
+// The purpose of this test is to verify that an option comprising
+// single data field with binary data can be used and that this
+// binary data is properly initialized to a default value. This
+// test also checks that it is possible to override this default
+// value.
+TEST_F(OptionCustomTest, setBinaryData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "binary");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default binary value.
+ OptionBuffer buf;
+ ASSERT_NO_THROW(option->readBinary());
+ // The buffer is by default empty.
+ EXPECT_TRUE(buf.empty());
+ // Prepare input buffer with some dummy data.
+ OptionBuffer buf_in(10);
+ for (int i = 0; i < buf_in.size(); ++i) {
+ buf_in[i] = i;
+ }
+ // Try to override the default binary buffer.
+ ASSERT_NO_THROW(option->writeBinary(buf_in));
+ // And check that it has been actually overriden.
+ ASSERT_NO_THROW(buf = option->readBinary());
+ ASSERT_EQ(buf_in.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that an option comprising
+// single boolean data field can be created and that its default
+// value can be overriden by a new value.
+TEST_F(OptionCustomTest, setBooleanData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Check that the default boolean value is false.
+ bool value = false;
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_FALSE(value);
+ // Check that we can override the default value.
+ ASSERT_NO_THROW(option->writeBoolean(true));
+ // Finally, check that it has been actually overriden.
+ ASSERT_NO_THROW(value = option->readBoolean());
+ EXPECT_TRUE(value);
+}
+
+/// The purpose of this test is to verify that the data field value
+/// can be overriden by a new value.
+TEST_F(OptionCustomTest, setUint32Data) {
+ // Create a definition of an option that holds single
+ // uint32 value.
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint32");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The default value for integer data fields is 0.
+ uint32_t value = 0;
+ ASSERT_NO_THROW(option->readInteger<uint32_t>());
+ EXPECT_EQ(0, value);
+
+ // Try to set the data field value to something different
+ // than 0.
+ ASSERT_NO_THROW(option->writeInteger<uint32_t>(1234));
+
+ // Verify that it has been set.
+ ASSERT_NO_THROW(value = option->readInteger<uint32_t>());
+ EXPECT_EQ(1234, value);
+}
+
+// The purpose of this test is to verify that an option comprising
+// single IPv4 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv4AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("127.0.0.1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("0.0.0.0", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("192.168.0.1", address.toText());
+}
+
+// The purpose of this test is to verify that an opton comprising
+// single IPv6 address can be created and that this address can
+// be overriden by a new value.
+TEST_F(OptionCustomTest, setIpv6AddressData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ asiolink::IOAddress address("::1");
+ ASSERT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("::", address.toText());
+
+ EXPECT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::1")));
+
+ EXPECT_NO_THROW(address = option->readAddress());
+ EXPECT_EQ("2001:db8:1::1", address.toText());
+}
+
+// The purpose of this test is to verify that an option comprising
+// single string value can be created and that this value
+// is initialized to the default value. Also, this test checks that
+// this value can be overwritten by a new value.
+TEST_F(OptionCustomTest, setStringData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "string");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Get the default value of the option.
+ std::string value;
+ ASSERT_NO_THROW(value = option->readString());
+ // By default the string data field is empty.
+ EXPECT_TRUE(value.empty());
+ // Write some text to this field.
+ ASSERT_NO_THROW(option->writeString("hello world"));
+ // Check that it has been actually written.
+ EXPECT_NO_THROW(value = option->readString());
+ EXPECT_EQ("hello world", value);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// a default FQDN value can be created and that this value can be
+/// overriden after the option has been created.
+TEST_F(OptionCustomTest, setFqdnData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "fqdn");
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+ // Read a default FQDN value from the option.
+ std::string fqdn;
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ(".", fqdn);
+ // Try override the default FQDN value.
+ ASSERT_NO_THROW(option->writeFqdn("example.com"));
+ // Check that the value has been actually overriden.
+ ASSERT_NO_THROW(fqdn = option->readFqdn());
+ EXPECT_EQ("example.com.", fqdn);
+}
+
+// The purpose of this test is to verify that an option carrying
+// an array of boolean values can be created with no values
+// initially and that values can be later added to it.
+TEST_F(OptionCustomTest, setBooleanDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "boolean", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add some boolean values to it.
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+ ASSERT_NO_THROW(option->addArrayDataField(false));
+ ASSERT_NO_THROW(option->addArrayDataField(true));
+
+ // Verify that the new data fields can be added.
+ bool value0 = false;
+ ASSERT_NO_THROW(value0 = option->readBoolean(0));
+ EXPECT_TRUE(value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ bool value2 = false;
+ ASSERT_NO_THROW(value2 = option->readBoolean(2));
+ EXPECT_TRUE(value2);
+}
+
+// The purpose of this test is to verify that am option carying
+// an array of 16-bit signed integer values can be created with
+// no values initially and that the values can be later added to it.
+TEST_F(OptionCustomTest, setUint16DataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "uint16", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array should contain no values.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new data fields holding integer values.
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(67));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(876));
+ ASSERT_NO_THROW(option->addArrayDataField<uint16_t>(32222));
+
+ // We should now have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that the values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(67, value0);
+ uint16_t value1;
+ ASSERT_NO_THROW(value1 = option->readInteger<uint16_t>(1));
+ EXPECT_EQ(876, value1);
+ uint16_t value2;
+ ASSERT_NO_THROW(value2 = option->readInteger<uint16_t>(2));
+ EXPECT_EQ(32222, value2);
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv4 address can be created with no addresses and that
+/// multiple IPv4 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv4AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 232, "ipv4-address", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4));
+ );
+ ASSERT_TRUE(option);
+
+ // Expect that the array does not contain any data fields yet.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 IPv4 addresses.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("192.168.0.3")));
+
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that all IP addresses have been set correctly.
+ IOAddress address0("127.0.0.1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("192.168.0.1", address0.toText());
+ IOAddress address1("127.0.0.1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("192.168.0.2", address1.toText());
+ IOAddress address2("127.0.0.1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("192.168.0.3", address2.toText());
+
+ // Add invalid address (IPv6 instead of IPv4).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("2001:db8:1::1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+/// The purpose of this test is to verify that an option comprising
+/// array of IPv6 address can be created with no addresses and that
+/// multiple IPv6 addresses can be added to it after creation.
+TEST_F(OptionCustomTest, setIpv6AddressDataArray) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // Initially, the array does not contain any data fields.
+ ASSERT_EQ(0, option->getDataFieldsNum());
+
+ // Add 3 new IPv6 addresses into the array.
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::1")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::2")));
+ ASSERT_NO_THROW(option->addArrayDataField(IOAddress("2001:db8:1::3")));
+
+ // We should have now 3 addresses added.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // Check that they have correct values set.
+ IOAddress address0("::1");
+ ASSERT_NO_THROW(address0 = option->readAddress(0));
+ EXPECT_EQ("2001:db8:1::1", address0.toText());
+ IOAddress address1("::1");
+ ASSERT_NO_THROW(address1 = option->readAddress(1));
+ EXPECT_EQ("2001:db8:1::2", address1.toText());
+ IOAddress address2("::1");
+ ASSERT_NO_THROW(address2 = option->readAddress(2));
+ EXPECT_EQ("2001:db8:1::3", address2.toText());
+
+ // Add invalid address (IPv4 instead of IPv6).
+ EXPECT_THROW(
+ option->addArrayDataField(IOAddress("192.168.0.1")),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+TEST_F(OptionCustomTest, setRecordData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("fqdn"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv4-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ // Create an option and let the data field be initialized
+ // to default value (do not provide any data buffer).
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6));
+ );
+ ASSERT_TRUE(option);
+
+ // The number of elements should be equal to number of elements
+ // in the record.
+ ASSERT_EQ(6, option->getDataFieldsNum());
+
+ // Check that the default values have been correctly set.
+ uint16_t value0;
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(0, value0);
+ bool value1 = true;
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_FALSE(value1);
+ std::string value2;
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ(".", value2);
+ IOAddress value3("127.0.0.1");
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("0.0.0.0", value3.toText());
+ IOAddress value4("2001:db8:1::1");
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("::", value4.toText());
+ std::string value5 = "xyz";
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_TRUE(value5.empty());
+
+ // Override each value with a new value.
+ ASSERT_NO_THROW(option->writeInteger<uint16_t>(1234, 0));
+ ASSERT_NO_THROW(option->writeBoolean(true, 1));
+ ASSERT_NO_THROW(option->writeFqdn("example.com", 2));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("192.168.0.1"), 3));
+ ASSERT_NO_THROW(option->writeAddress(IOAddress("2001:db8:1::100"), 4));
+ ASSERT_NO_THROW(option->writeString("hello world", 5));
+
+ // Check that the new values have been correctly set.
+ ASSERT_NO_THROW(value0 = option->readInteger<uint16_t>(0));
+ EXPECT_EQ(1234, value0);
+ ASSERT_NO_THROW(value1 = option->readBoolean(1));
+ EXPECT_TRUE(value1);
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("example.com.", value2);
+ ASSERT_NO_THROW(value3 = option->readAddress(3));
+ EXPECT_EQ("192.168.0.1", value3.toText());
+ ASSERT_NO_THROW(value4 = option->readAddress(4));
+ EXPECT_EQ("2001:db8:1::100", value4.toText());
+ ASSERT_NO_THROW(value5 = option->readString(5));
+ EXPECT_EQ(value5, "hello world");
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv4 custom option works correctly.
+TEST_F(OptionCustomTest, pack4) {
+ OptionDefinition opt_def("OPTION_FOO", 234, "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("uint8"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ OptionBuffer buf;
+ writeInt<uint8_t>(1, buf);
+ writeInt<uint16_t>(1000, buf);
+ writeInt<uint32_t>(100000, buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf));
+ );
+ ASSERT_TRUE(option);
+
+ util::OutputBuffer buf_out(7);
+ ASSERT_NO_THROW(option->pack(buf_out));
+ ASSERT_EQ(9, buf_out.getLength());
+
+ // The original buffer holds the option data but it lacks a header.
+ // We append data length and option code so as it can be directly
+ // compared with the output buffer that holds whole option.
+ buf.insert(buf.begin(), 7);
+ buf.insert(buf.begin(), 234);
+
+ // Validate the buffer.
+ EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that pack function for
+// DHCPv6 custom option works correctly.
+TEST_F(OptionCustomTest, pack6) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("boolean"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint16"));
+ ASSERT_NO_THROW(opt_def.addRecordField("string"));
+
+ OptionBuffer buf;
+ buf.push_back(1);
+ writeInt<uint16_t>(1000, buf);
+ writeString("hello world", buf);
+
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ util::OutputBuffer buf_out(buf.size() + option->getHeaderLen());
+ ASSERT_NO_THROW(option->pack(buf_out));
+ ASSERT_EQ(buf.size() + option->getHeaderLen(), buf_out.getLength());
+
+ // The original buffer holds the option data but it lacks a header.
+ // We append data length and option code so as it can be directly
+ // compared with the output buffer that holds whole option.
+ OptionBuffer tmp;
+ writeInt<uint16_t>(1000, tmp);
+ writeInt<uint16_t>(buf.size(), tmp);
+ buf.insert(buf.begin(), tmp.begin(), tmp.end());
+
+ // Validate the buffer.
+ EXPECT_EQ(0, memcmp(&buf[0], buf_out.getData(), 7));
+}
+
+// The purpose of this test is to verify that unpack function works
+// correctly for a custom option.
+TEST_F(OptionCustomTest, unpack) {
+ OptionDefinition opt_def("OPTION_FOO", 231, "ipv4-address", true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("192.168.0.1"));
+ addresses.push_back(IOAddress("127.0.0.1"));
+ addresses.push_back(IOAddress("10.10.1.2"));
+
+ // Store the collection of IPv4 addresses into the buffer.
+ OptionBuffer buf;
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv4 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+
+ // Remove all addresses we had added. We are going to replace
+ // them with a new set of addresses.
+ addresses.clear();
+
+ // Add new addresses.
+ addresses.push_back(IOAddress("10.1.2.3"));
+ addresses.push_back(IOAddress("85.26.43.234"));
+
+ // Clear the buffer as we need to store new addresses in it.
+ buf.clear();
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Perform 'unpack'.
+ ASSERT_NO_THROW(option->unpack(buf.begin(), buf.end()));
+
+ // Now we should have only 2 data fields.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Verify that the addresses have been overwritten.
+ for (int i = 0; i < 2; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+}
+
+// The purpose of this test is to verify that new data can be set for
+// a custom option.
+TEST_F(OptionCustomTest, setData) {
+ OptionDefinition opt_def("OPTION_FOO", 1000, "ipv6-address", true);
+
+ // Initialize reference data.
+ std::vector<IOAddress> addresses;
+ addresses.push_back(IOAddress("2001:db8:1::3"));
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::3"));
+
+ // Store the collection of IPv6 addresses into the buffer.
+ OptionBuffer buf;
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end()));
+ );
+ ASSERT_TRUE(option);
+
+ // We should have 3 data fields.
+ ASSERT_EQ(3, option->getDataFieldsNum());
+
+ // We expect 3 IPv6 addresses being stored in the option.
+ for (int i = 0; i < 3; ++i) {
+ IOAddress address("fe80::4");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+
+ // Clear addresses we had previously added.
+ addresses.clear();
+
+ // Store new addresses.
+ addresses.push_back(IOAddress("::1"));
+ addresses.push_back(IOAddress("fe80::10"));
+
+ // Clear the buffer as we need to store new addresses in it.
+ buf.clear();
+ for (int i = 0; i < addresses.size(); ++i) {
+ writeAddress(addresses[i], buf);
+ }
+
+ // Replace the option data.
+ ASSERT_NO_THROW(option->setData(buf.begin(), buf.end()));
+
+ // Now we should have only 2 data fields.
+ ASSERT_EQ(2, option->getDataFieldsNum());
+
+ // Check that it has been replaced.
+ for (int i = 0; i < 2; ++i) {
+ IOAddress address("10.10.10.10");
+ ASSERT_NO_THROW(address = option->readAddress(i));
+ EXPECT_EQ(addresses[i].toText(), address.toText());
+ }
+}
+
+// The purpose of this test is to verify that an invalid index
+// value can't be used to access option data fields.
+TEST_F(OptionCustomTest, invalidIndex) {
+ OptionDefinition opt_def("OPTION_FOO", 999, "uint32", true);
+
+ OptionBuffer buf;
+ for (int i = 0; i < 10; ++i) {
+ writeInt<uint32_t>(i, buf);
+ }
+
+ // Use the input buffer to create custom option.
+ boost::scoped_ptr<OptionCustom> option;
+ ASSERT_NO_THROW(
+ option.reset(new OptionCustom(opt_def, Option::V6, buf));
+ );
+ ASSERT_TRUE(option);
+
+ // We expect that there are 10 uint32_t values stored in
+ // the option. The 10th element is accessed by index eq 9.
+ // Check that 9 is accepted.
+ EXPECT_NO_THROW(option->readInteger<uint32_t>(9));
+
+ // Check that index value beyond 9 is not accepted.
+ EXPECT_THROW(option->readInteger<uint32_t>(10), isc::OutOfRange);
+ EXPECT_THROW(option->readInteger<uint32_t>(11), isc::OutOfRange);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_data_types_unittest.cc b/src/lib/dhcp/tests/option_data_types_unittest.cc
new file mode 100644
index 0000000..fd5294c
--- /dev/null
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -0,0 +1,483 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <dhcp/option_data_types.h>
+#include <gtest/gtest.h>
+
+using namespace isc;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test class for option data type utilities.
+class OptionDataTypesTest : public ::testing::Test {
+public:
+
+ /// @brief Constructor.
+ OptionDataTypesTest() { }
+
+ /// @brief Write IP address into a buffer.
+ ///
+ /// @param address address to be written.
+ /// @param [out] buf output buffer.
+ void writeAddress(const asiolink::IOAddress& address,
+ std::vector<uint8_t>& buf) {
+ const std::vector<uint8_t>& vec = address.toBytes();
+ buf.insert(buf.end(), vec.begin(), vec.end());
+ }
+
+ /// @brief Write integer (signed or unsigned) into a buffer.
+ ///
+ /// @param value integer value.
+ /// @param [out] buf output buffer.
+ /// @tparam integer type.
+ template<typename T>
+ void writeInt(T value, std::vector<uint8_t>& buf) {
+ for (int i = 0; i < sizeof(T); ++i) {
+ buf.push_back(value >> ((sizeof(T) - i - 1) * 8) & 0xFF);
+ }
+ }
+
+ /// @brief Write a string into a buffer.
+ ///
+ /// @param value string to be written into a buffer.
+ /// @param buf output buffer.
+ void writeString(const std::string& value,
+ std::vector<uint8_t>& buf) {
+ buf.resize(buf.size() + value.size());
+ std::copy_backward(value.c_str(), value.c_str() + value.size(),
+ buf.end());
+ }
+};
+
+// The goal of this test is to verify that an IPv4 address being
+// stored in a buffer (wire format) can be read into IOAddress
+// object.
+TEST_F(OptionDataTypesTest, readAddress) {
+ // Create some IPv4 address.
+ asiolink::IOAddress address("192.168.0.1");
+ // And store it in a buffer in a wire format.
+ std::vector<uint8_t> buf;
+ writeAddress(address, buf);
+
+ // Now, try to read the IP address with a utility function
+ // being under test.
+ asiolink::IOAddress address_out("127.0.0.1");
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET));
+
+ // Check that the read address matches address that
+ // we used as input.
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Check that an attempt to read the buffer as IPv6 address
+ // causes an error as the IPv6 address needs at least 16 bytes
+ // long buffer.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Do another test like this for IPv6 address.
+ address = asiolink::IOAddress("2001:db8:1:0::1");
+ writeAddress(address, buf);
+ EXPECT_NO_THROW(address_out = OptionDataTypeUtil::readAddress(buf, AF_INET6));
+ EXPECT_EQ(address.toText(), address_out.toText());
+
+ // Truncate the buffer and expect an error to be reported when
+ // trying to read it.
+ buf.resize(buf.size() - 1);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readAddress(buf, AF_INET6),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The goal of this test is to verify that an IPv6 address
+// is properly converted to wire format and stored in a
+// buffer.
+TEST_F(OptionDataTypesTest, writeAddress) {
+ // Encode an IPv6 address 2001:db8:1::1 in wire format.
+ // This will be used as reference data to validate if
+ // an IPv6 address is stored in a buffer properly.
+ const uint8_t data[] = {
+ 0x20, 0x01, 0x0d, 0xb8, 0x0, 0x1, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1
+ };
+ std::vector<uint8_t> buf_in(data, data + sizeof(data));
+
+ // Create IPv6 address object.
+ asiolink::IOAddress address("2001:db8:1::1");
+ // Define the output buffer to write IP address to.
+ std::vector<uint8_t> buf_out;
+ // Write the address to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ // Make sure that input and output buffers have the same size
+ // so we can compare them.
+ ASSERT_EQ(buf_in.size(), buf_out.size());
+ // And finally compare them.
+ EXPECT_TRUE(std::equal(buf_in.begin(), buf_in.end(), buf_out.begin()));
+
+ buf_out.clear();
+
+ // Do similar test for IPv4 address.
+ address = asiolink::IOAddress("192.168.0.1");
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeAddress(address, buf_out));
+ ASSERT_EQ(4, buf_out.size());
+ // Verify that the IP address has been written correctly.
+ EXPECT_EQ(192, buf_out[0]);
+ EXPECT_EQ(168, buf_out[1]);
+ EXPECT_EQ(0, buf_out[2]);
+ EXPECT_EQ(1, buf_out[3]);
+}
+
+// The purpose of this test is to verify that binary data represented
+// as a string of hexadecimal digits can be written to a buffer.
+TEST_F(OptionDataTypesTest, writeBinary) {
+ // Prepare the reference data.
+ const char data[] = {
+ 0x0, 0x1, 0x2, 0x3, 0x4, 0x5,
+ 0x6, 0x7, 0x8, 0x9, 0xA, 0xB
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+ // Create empty vector where binary data will be written to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(
+ OptionDataTypeUtil::writeBinary("000102030405060708090A0B", buf)
+ );
+ // Verify that the buffer contains valid data.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that the boolean value stored
+// in a buffer is correctly read from this buffer.
+TEST_F(OptionDataTypesTest, readBool) {
+ // Create an input buffer.
+ std::vector<uint8_t> buf;
+ // 'true' value is encoded as 1 ('false' is encoded as 0)
+ buf.push_back(1);
+
+ // Read the value from the buffer.
+ bool value = false;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ // Verify the value.
+ EXPECT_TRUE(value);
+ // Check if 'false' is read correctly either.
+ buf[0] = 0;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readBool(buf);
+ );
+ EXPECT_FALSE(value);
+
+ // Check that invalid value causes exception.
+ buf[0] = 5;
+ ASSERT_THROW(
+ OptionDataTypeUtil::readBool(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that boolean values
+// are correctly encoded in a buffer as '1' for 'true' and
+// '0' for 'false' values.
+TEST_F(OptionDataTypesTest, writeBool) {
+ // Create a buffer we will write to.
+ std::vector<uint8_t> buf;
+ // Write the 'true' value to the buffer.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(true, buf));
+ // We should now have 'true' value stored in a buffer.
+ ASSERT_EQ(1, buf.size());
+ EXPECT_EQ(buf[0], 1);
+ // Let's append another value to make sure that it is not always
+ // 'true' value being written.
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeBool(false, buf));
+ ASSERT_EQ(2, buf.size());
+ // Check that the first value has not changed.
+ EXPECT_EQ(buf[0], 1);
+ // Check the the second value is correct.
+ EXPECT_EQ(buf[1], 0);
+}
+
+// The purpose of this test is to verify that the integer values
+// of different types are correctly read from a buffer.
+TEST_F(OptionDataTypesTest, readInt) {
+ std::vector<uint8_t> buf;
+
+ // Write an 8-bit unsigned integer value to the buffer.
+ writeInt<uint8_t>(129, buf);
+ uint8_t valueUint8 = 0;
+ // Read the value and check that it is valid.
+ ASSERT_NO_THROW(
+ valueUint8 = OptionDataTypeUtil::readInt<uint8_t>(buf);
+ );
+ EXPECT_EQ(129, valueUint8);
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Clear the buffer for the next check we are going to do.
+ buf.clear();
+
+ // Test uint16_t value.
+ writeInt<uint16_t>(1234, buf);
+ uint16_t valueUint16 = 0;
+ ASSERT_NO_THROW(
+ valueUint16 = OptionDataTypeUtil::readInt<uint16_t>(buf);
+ );
+ EXPECT_EQ(1234, valueUint16);
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<uint32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ buf.clear();
+
+ // Test uint32_t value.
+ writeInt<uint32_t>(56789, buf);
+ uint32_t valueUint32 = 0;
+ ASSERT_NO_THROW(
+ valueUint32 = OptionDataTypeUtil::readInt<uint32_t>(buf);
+ );
+ EXPECT_EQ(56789, valueUint32);
+ buf.clear();
+
+ // Test int8_t value.
+ writeInt<int8_t>(-65, buf);
+ int8_t valueInt8 = 0;
+ ASSERT_NO_THROW(
+ valueInt8 = OptionDataTypeUtil::readInt<int8_t>(buf);
+ );
+ EXPECT_EQ(-65, valueInt8);
+ buf.clear();
+
+ // Try to read 16-bit value from a buffer holding 8-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int16_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int16_t value.
+ writeInt<int16_t>(2345, buf);
+ int32_t valueInt16 = 0;
+ ASSERT_NO_THROW(
+ valueInt16 = OptionDataTypeUtil::readInt<int16_t>(buf);
+ );
+ EXPECT_EQ(2345, valueInt16);
+ buf.clear();
+
+ // Try to read 32-bit value from a buffer holding 16-bit value.
+ // This should result in an exception.
+ EXPECT_THROW(
+ OptionDataTypeUtil::readInt<int32_t>(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Test int32_t value.
+ writeInt<int32_t>(-16543, buf);
+ int32_t valueInt32 = 0;
+ ASSERT_NO_THROW(
+ valueInt32 = OptionDataTypeUtil::readInt<int32_t>(buf);
+ );
+ EXPECT_EQ(-16543, valueInt32);
+
+ buf.clear();
+}
+
+// The purpose of this test is to verify that integer values of different
+// types are correctly written to a buffer.
+TEST_F(OptionDataTypesTest, writeInt) {
+ // Prepare the reference buffer.
+ const uint8_t data[] = {
+ 0x7F, // 127
+ 0x03, 0xFF, // 1023
+ 0x00, 0x00, 0x10, 0x00, // 4096
+ 0xFF, 0xFF, 0xFC, 0x00, // -1024
+ 0x02, 0x00, // 512
+ 0x81 // -127
+ };
+ std::vector<uint8_t> buf_ref(data, data + sizeof(data));
+
+ // Fill in the buffer with data. Each write operation appends an
+ // integer value. Eventually the buffer holds all values and should
+ // match with the reference buffer.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint8_t>(127, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint16_t>(1023, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<uint32_t>(4096, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int32_t>(-1024, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int16_t>(512, buf));
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeInt<int8_t>(-127, buf));
+
+ // Make sure that the buffer has the same size as the reference
+ // buffer.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ // Compare buffers.
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+// The purpose of this test is to verify that FQDN is read from
+// a buffer and returned as a text. The representation of the FQDN
+// in the buffer complies with RFC1035, section 3.1.
+// This test also checks that if invalid (truncated) FQDN is stored
+// in a buffer the appropriate exception is returned when trying to
+// read it as a string.
+TEST_F(OptionDataTypesTest, readFqdn) {
+ // The binary representation of the "mydomain.example.com".
+ // Values: 8, 7, 3 and 0 specify the lengths of subsequent
+ // labels within the FQDN.
+ const char data[] = {
+ 8, 109, 121, 100, 111, 109, 97, 105, 110, // "mydomain"
+ 7, 101, 120, 97, 109, 112, 108, 101, // "example"
+ 3, 99, 111, 109, // "com"
+ 0
+ };
+
+ // Make a vector out of the data.
+ std::vector<uint8_t> buf(data, data + sizeof(data));
+
+ // Read the buffer as FQDN and verify its correctness.
+ std::string fqdn;
+ EXPECT_NO_THROW(fqdn = OptionDataTypeUtil::readFqdn(buf));
+ EXPECT_EQ("mydomain.example.com.", fqdn);
+
+ // By resizing the buffer we simulate truncation. The first
+ // length field (8) indicate that the first label's size is
+ // 8 but the actual buffer size is 5. Expect that conversion
+ // fails.
+ buf.resize(5);
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Another special case: provide an empty buffer.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::readFqdn(buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that FQDN's syntax is validated
+// and that FQDN is correctly written to a buffer in a format described
+// in RFC1035 section 3.1.
+TEST_F(OptionDataTypesTest, writeFqdn) {
+ // Create empty buffer. The FQDN will be written to it.
+ OptionBuffer buf;
+ // Write a domain name into the buffer in the format described
+ // in RFC1035 section 3.1. This function should not throw
+ // exception because domain name is well formed.
+ EXPECT_NO_THROW(
+ OptionDataTypeUtil::writeFqdn("mydomain.example.com", buf)
+ );
+ // The length of the data is 22 (8 bytes for "mydomain" label,
+ // 7 bytes for "example" label, 3 bytes for "com" label and
+ // finally 4 bytes positions between labels where length
+ // information is stored.
+ ASSERT_EQ(22, buf.size());
+
+ // Verify that length fields between labels hold valid values.
+ EXPECT_EQ(8, buf[0]); // length of "mydomain"
+ EXPECT_EQ(7, buf[9]); // length of "example"
+ EXPECT_EQ(3, buf[17]); // length of "com"
+ EXPECT_EQ(0, buf[21]); // zero byte at the end.
+
+ // Verify that labels are valid.
+ std::string label0(buf.begin() + 1, buf.begin() + 9);
+ EXPECT_EQ("mydomain", label0);
+
+ std::string label1(buf.begin() + 10, buf.begin() + 17);
+ EXPECT_EQ("example", label1);
+
+ std::string label2(buf.begin() + 18, buf.begin() + 21);
+ EXPECT_EQ("com", label2);
+
+ // The tested function is supposed to append data to a buffer
+ // so let's check that it is a case by appending another domain.
+ OptionDataTypeUtil::writeFqdn("hello.net", buf);
+
+ // The buffer length should be now longer.
+ ASSERT_EQ(33, buf.size());
+
+ // Check the length fields for new labels being appended.
+ EXPECT_EQ(5, buf[22]);
+ EXPECT_EQ(3, buf[28]);
+
+ // And check that labels are ok.
+ std::string label3(buf.begin() + 23, buf.begin() + 28);
+ EXPECT_EQ("hello", label3);
+
+ std::string label4(buf.begin() + 29, buf.begin() + 32);
+ EXPECT_EQ("net", label4);
+
+ // Check that invalid (empty) FQDN is rejected and expected
+ // exception type is thrown.
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+
+ // Check another invalid domain name (with repeated dot).
+ buf.clear();
+ EXPECT_THROW(
+ OptionDataTypeUtil::writeFqdn("example..com", buf),
+ isc::dhcp::BadDataTypeCast
+ );
+}
+
+// The purpose of this test is to verify that the string
+// can be read from a buffer correctly.
+TEST_F(OptionDataTypesTest, readString) {
+ // Prepare a buffer with some string in it.
+ std::vector<uint8_t> buf;
+ writeString("hello world", buf);
+
+ // Read the string from the buffer.
+ std::string value;
+ ASSERT_NO_THROW(
+ value = OptionDataTypeUtil::readString(buf);
+ );
+ // Check that it is valid.
+ EXPECT_EQ("hello world", value);
+}
+
+// The purpose of this test is to verify that a string can be
+// stored in a buffer correctly.
+TEST_F(OptionDataTypesTest, writeString) {
+ // Prepare a buffer with a reference data.
+ std::vector<uint8_t> buf_ref;
+ writeString("hello world!", buf_ref);
+ // Create empty buffer we will write to.
+ std::vector<uint8_t> buf;
+ ASSERT_NO_THROW(OptionDataTypeUtil::writeString("hello world!", buf));
+ // Compare two buffers.
+ ASSERT_EQ(buf_ref.size(), buf.size());
+ EXPECT_TRUE(std::equal(buf_ref.begin(), buf_ref.end(), buf.begin()));
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index bdf3edf..310c7bf 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -21,9 +21,10 @@
#include <dhcp/option6_addrlst.h>
#include <dhcp/option6_ia.h>
#include <dhcp/option6_iaaddr.h>
-#include <dhcp/option6_int.h>
-#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/option_definition.h>
+#include <dhcp/option_int.h>
+#include <dhcp/option_int_array.h>
#include <exceptions/exceptions.h>
#include <boost/pointer_cast.hpp>
@@ -47,32 +48,35 @@ public:
OptionDefinitionTest() { }
};
+// The purpose of this test is to verify that OptionDefinition
+// constructor initializes its members correctly.
TEST_F(OptionDefinitionTest, constructor) {
// Specify the option data type as string. This should get converted
// to enum value returned by getType().
OptionDefinition opt_def1("OPTION_CLIENTID", 1, "string");
EXPECT_EQ("OPTION_CLIENTID", opt_def1.getName());
+
EXPECT_EQ(1, opt_def1.getCode());
- EXPECT_EQ(OptionDefinition::STRING_TYPE, opt_def1.getType());
+ EXPECT_EQ(OPT_STRING_TYPE, opt_def1.getType());
EXPECT_FALSE(opt_def1.getArrayType());
EXPECT_NO_THROW(opt_def1.validate());
// Specify the option data type as an enum value.
OptionDefinition opt_def2("OPTION_RAPID_COMMIT", 14,
- OptionDefinition::EMPTY_TYPE);
+ OPT_EMPTY_TYPE);
EXPECT_EQ("OPTION_RAPID_COMMIT", opt_def2.getName());
EXPECT_EQ(14, opt_def2.getCode());
- EXPECT_EQ(OptionDefinition::EMPTY_TYPE, opt_def2.getType());
+ EXPECT_EQ(OPT_EMPTY_TYPE, opt_def2.getType());
EXPECT_FALSE(opt_def2.getArrayType());
EXPECT_NO_THROW(opt_def1.validate());
// Check if it is possible to set that option is an array.
OptionDefinition opt_def3("OPTION_NIS_SERVERS", 27,
- OptionDefinition::IPV6_ADDRESS_TYPE,
+ OPT_IPV6_ADDRESS_TYPE,
true);
EXPECT_EQ("OPTION_NIS_SERVERS", opt_def3.getName());
EXPECT_EQ(27, opt_def3.getCode());
- EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, opt_def3.getType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, opt_def3.getType());
EXPECT_TRUE(opt_def3.getArrayType());
EXPECT_NO_THROW(opt_def3.validate());
@@ -81,23 +85,27 @@ TEST_F(OptionDefinitionTest, constructor) {
// it has been created.
EXPECT_NO_THROW(
OptionDefinition opt_def4("OPTION_SERVERID",
- OptionDefinition::UNKNOWN_TYPE + 10,
- OptionDefinition::STRING_TYPE);
+ OPT_UNKNOWN_TYPE + 10,
+ OPT_STRING_TYPE);
);
}
+// The purpose of this test is to verify that various data fields
+// can be specified for an option definition when this definition
+// is marked as 'record' and that fields can't be added if option
+// definition is not marked as 'record'.
TEST_F(OptionDefinitionTest, addRecordField) {
// We can only add fields to record if the option type has been
// specified as 'record'. We try all other types but 'record'
// here and expect exception to be thrown.
- for (int i = 0; i < OptionDefinition::UNKNOWN_TYPE; ++i) {
+ for (int i = 0; i < OPT_UNKNOWN_TYPE; ++i) {
// Do not try for 'record' type because this is the only
// type for which adding record will succeed.
- if (i == OptionDefinition::RECORD_TYPE) {
+ if (i == OPT_RECORD_TYPE) {
continue;
}
OptionDefinition opt_def("OPTION_IAADDR", 5,
- static_cast<OptionDefinition::DataType>(i));
+ static_cast<OptionDataType>(i));
EXPECT_THROW(opt_def.addRecordField("uint8"), isc::InvalidOperation);
}
@@ -106,54 +114,114 @@ TEST_F(OptionDefinitionTest, addRecordField) {
EXPECT_NO_THROW(opt_def.addRecordField("ipv6-address"));
EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
// It should not matter if we specify field type by its name or using enum.
- EXPECT_NO_THROW(opt_def.addRecordField(OptionDefinition::UINT32_TYPE));
+ EXPECT_NO_THROW(opt_def.addRecordField(OPT_UINT32_TYPE));
// Check what we have actually added.
OptionDefinition::RecordFieldsCollection fields = opt_def.getRecordFields();
ASSERT_EQ(3, fields.size());
- EXPECT_EQ(OptionDefinition::IPV6_ADDRESS_TYPE, fields[0]);
- EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[1]);
- EXPECT_EQ(OptionDefinition::UINT32_TYPE, fields[2]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, fields[0]);
+ EXPECT_EQ(OPT_UINT32_TYPE, fields[1]);
+ EXPECT_EQ(OPT_UINT32_TYPE, fields[2]);
// Let's try some more negative scenarios: use invalid data types.
EXPECT_THROW(opt_def.addRecordField("unknown_type_xyz"), isc::BadValue);
- OptionDefinition::DataType invalid_type =
- static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE + 10);
+ OptionDataType invalid_type =
+ static_cast<OptionDataType>(OPT_UNKNOWN_TYPE + 10);
EXPECT_THROW(opt_def.addRecordField(invalid_type), isc::BadValue);
+
+ // It is bad if we use 'record' option type but don't specify
+ // at least two fields.
+ OptionDefinition opt_def2("OPTION_EMPTY_RECORD", 100, "record");
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+ opt_def2.addRecordField("uint8");
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
+ opt_def2.addRecordField("uint32");
+ EXPECT_NO_THROW(opt_def2.validate());
}
+// The purpose of this test is to check that validate() function
+// reports errors for invalid option definitions.
TEST_F(OptionDefinitionTest, validate) {
// Not supported option type string is not allowed.
OptionDefinition opt_def1("OPTION_CLIENTID", D6O_CLIENTID, "non-existent-type");
- EXPECT_THROW(opt_def1.validate(), isc::OutOfRange);
+ EXPECT_THROW(opt_def1.validate(), MalformedOptionDefinition);
// Not supported option type enum value is not allowed.
- OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OptionDefinition::UNKNOWN_TYPE);
- EXPECT_THROW(opt_def2.validate(), isc::OutOfRange);
+ OptionDefinition opt_def2("OPTION_CLIENTID", D6O_CLIENTID, OPT_UNKNOWN_TYPE);
+ EXPECT_THROW(opt_def2.validate(), MalformedOptionDefinition);
OptionDefinition opt_def3("OPTION_CLIENTID", D6O_CLIENTID,
- static_cast<OptionDefinition::DataType>(OptionDefinition::UNKNOWN_TYPE
+ static_cast<OptionDataType>(OPT_UNKNOWN_TYPE
+ 2));
- EXPECT_THROW(opt_def3.validate(), isc::OutOfRange);
-
+ EXPECT_THROW(opt_def3.validate(), MalformedOptionDefinition);
+
// Empty option name is not allowed.
OptionDefinition opt_def4("", D6O_CLIENTID, "string");
- EXPECT_THROW(opt_def4.validate(), isc::BadValue);
+ EXPECT_THROW(opt_def4.validate(), MalformedOptionDefinition);
// Option name must not contain spaces.
OptionDefinition opt_def5(" OPTION_CLIENTID", D6O_CLIENTID, "string");
- EXPECT_THROW(opt_def5.validate(), isc::BadValue);
+ EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
+ // Option name must not contain spaces.
OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
- EXPECT_THROW(opt_def6.validate(), isc::BadValue);
+ EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+
+ // Option name may contain lower case letters.
+ OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def7.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def8("option_123", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def8.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def9.validate());
+
+ // Using hyphen or undescore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
+ // Having array of strings does not make sense because there is no way
+ // to determine string's length.
+ OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+ EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
+
+ // It does not make sense to have string field within the record before
+ // other fields because there is no way to determine the length of this
+ // string and thus there is no way to determine where the other field
+ // begins.
+ OptionDefinition opt_def15("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def15.addRecordField("string");
+ opt_def15.addRecordField("uint16");
+ EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
+
+ // ... but it is ok if the string value is the last one.
+ OptionDefinition opt_def16("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def16.addRecordField("uint8");
+ opt_def16.addRecordField("string");
}
-TEST_F(OptionDefinitionTest, factoryAddrList6) {
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses.
+TEST_F(OptionDefinitionTest, ipv6AddressArray) {
OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
"ipv6-address", true);
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Create a list of some V6 addresses.
std::vector<asiolink::IOAddress> addrs;
@@ -165,10 +233,9 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
// Write addresses to the buffer.
OptionBuffer buf(addrs.size() * asiolink::V6ADDRESS_LEN);
for (int i = 0; i < addrs.size(); ++i) {
- asio::ip::address_v6::bytes_type addr_bytes =
- addrs[i].getAddress().to_v6().to_bytes();
- ASSERT_EQ(asiolink::V6ADDRESS_LEN, addr_bytes.size());
- std::copy(addr_bytes.begin(), addr_bytes.end(),
+ const std::vector<uint8_t>& vec = addrs[i].toBytes();
+ ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(),
buf.begin() + i * asiolink::V6ADDRESS_LEN);
}
// Create DHCPv6 option from this buffer. Once option is created it is
@@ -176,7 +243,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
// the provided buffer.
OptionPtr option_v6;
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_NIS_SERVERS, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf);
);
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
boost::shared_ptr<Option6AddrLst> option_cast_v6 =
@@ -195,17 +262,64 @@ TEST_F(OptionDefinitionTest, factoryAddrList6) {
buf.insert(buf.end(), 1, 1);
// It should throw exception then.
EXPECT_THROW(
- factory(Option::V6, D6O_NIS_SERVERS, buf),
- isc::OutOfRange
+ opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS, buf),
+ InvalidOptionValue
);
}
-TEST_F(OptionDefinitionTest, factoryAddrList4) {
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv6 addresses will return an instance
+// of option with a list of IPv6 addresses. Array of IPv6 addresses
+// is specified as a vector of strings (each string represents single
+// IPv6 address).
+TEST_F(OptionDefinitionTest, ipv6AddressArrayTokenized) {
+ OptionDefinition opt_def("OPTION_NIS_SERVERS", D6O_NIS_SERVERS,
+ "ipv6-address", true);
+
+ // Create a vector of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:8329"));
+ addrs.push_back(asiolink::IOAddress("2001:0db8::ff00:0042:2319"));
+ addrs.push_back(asiolink::IOAddress("::1"));
+ addrs.push_back(asiolink::IOAddress("::2"));
+
+ // Create a vector of strings representing addresses given above.
+ std::vector<std::string> addrs_str;
+ for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+ it != addrs.end(); ++it) {
+ addrs_str.push_back(it->toText());
+ }
+
+ // Create DHCPv6 option using the list of IPv6 addresses given in the
+ // string form.
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_NIS_SERVERS,
+ addrs_str);
+ );
+ // Non-null pointer option is supposed to be returned and it
+ // should have Option6AddrLst type.
+ ASSERT_TRUE(option_v6);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(Option6AddrLst));
+ // Cast to the actual option type to get IPv6 addresses from it.
+ boost::shared_ptr<Option6AddrLst> option_cast_v6 =
+ boost::static_pointer_cast<Option6AddrLst>(option_v6);
+ // Check that cast was successful.
+ ASSERT_TRUE(option_cast_v6);
+ // Get the list of parsed addresses from the option object.
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v6->getAddresses();
+ // Returned addresses must match the addresses that have been used to create
+ // the option instance.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses.
+TEST_F(OptionDefinitionTest, ipv4AddressArray) {
OptionDefinition opt_def("OPTION_NAME_SERVERS", D6O_NIS_SERVERS,
"ipv4-address", true);
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Create a list of some V6 addresses.
std::vector<asiolink::IOAddress> addrs;
@@ -217,10 +331,9 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
// Write addresses to the buffer.
OptionBuffer buf(addrs.size() * asiolink::V4ADDRESS_LEN);
for (int i = 0; i < addrs.size(); ++i) {
- asio::ip::address_v4::bytes_type addr_bytes =
- addrs[i].getAddress().to_v4().to_bytes();
- ASSERT_EQ(asiolink::V4ADDRESS_LEN, addr_bytes.size());
- std::copy(addr_bytes.begin(), addr_bytes.end(),
+ const std::vector<uint8_t> vec = addrs[i].toBytes();
+ ASSERT_EQ(asiolink::V4ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(),
buf.begin() + i * asiolink::V4ADDRESS_LEN);
}
// Create DHCPv6 option from this buffer. Once option is created it is
@@ -228,7 +341,7 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
// the provided buffer.
OptionPtr option_v4;
ASSERT_NO_THROW(
- option_v4 = factory(Option::V4, DHO_NAME_SERVERS, buf)
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_NAME_SERVERS, buf)
);
ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
// Get the list of parsed addresses from the option object.
@@ -245,19 +358,66 @@ TEST_F(OptionDefinitionTest, factoryAddrList4) {
// fulfilled anymore.
buf.insert(buf.end(), 1, 1);
// It should throw exception then.
- EXPECT_THROW(factory(Option::V4, DHO_NIS_SERVERS, buf), isc::OutOfRange);
+ EXPECT_THROW(opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS, buf),
+ InvalidOptionValue);
+}
+
+// The purpose of this test is to verify that option definition
+// that comprises array of IPv4 addresses will return an instance
+// of option with a list of IPv4 addresses. The array of IPv4 addresses
+// is specified as a vector of strings (each string represents single
+// IPv4 address).
+TEST_F(OptionDefinitionTest, ipv4AddressArrayTokenized) {
+ OptionDefinition opt_def("OPTION_NIS_SERVERS", DHO_NIS_SERVERS,
+ "ipv4-address", true);
+
+ // Create a vector of some V6 addresses.
+ std::vector<asiolink::IOAddress> addrs;
+ addrs.push_back(asiolink::IOAddress("192.168.0.1"));
+ addrs.push_back(asiolink::IOAddress("172.16.1.1"));
+ addrs.push_back(asiolink::IOAddress("127.0.0.1"));
+ addrs.push_back(asiolink::IOAddress("213.41.23.12"));
+
+ // Create a vector of strings representing addresses given above.
+ std::vector<std::string> addrs_str;
+ for (std::vector<asiolink::IOAddress>::const_iterator it = addrs.begin();
+ it != addrs.end(); ++it) {
+ addrs_str.push_back(it->toText());
+ }
+
+ // Create DHCPv4 option using the list of IPv4 addresses given in the
+ // string form.
+ OptionPtr option_v4;
+ ASSERT_NO_THROW(
+ option_v4 = opt_def.optionFactory(Option::V4, DHO_NIS_SERVERS,
+ addrs_str);
+ );
+ // Non-null pointer option is supposed to be returned and it
+ // should have Option6AddrLst type.
+ ASSERT_TRUE(option_v4);
+ ASSERT_TRUE(typeid(*option_v4) == typeid(Option4AddrLst));
+ // Cast to the actual option type to get IPv4 addresses from it.
+ boost::shared_ptr<Option4AddrLst> option_cast_v4 =
+ boost::static_pointer_cast<Option4AddrLst>(option_v4);
+ // Check that cast was successful.
+ ASSERT_TRUE(option_cast_v4);
+ // Get the list of parsed addresses from the option object.
+ std::vector<asiolink::IOAddress> addrs_returned =
+ option_cast_v4->getAddresses();
+ // Returned addresses must match the addresses that have been used to create
+ // the option instance.
+ EXPECT_TRUE(std::equal(addrs.begin(), addrs.end(), addrs_returned.begin()));
}
-TEST_F(OptionDefinitionTest, factoryEmpty) {
+// The purpose of thie test is to verify that option definition for
+// 'empty' option can be created and that it returns 'empty' option.
+TEST_F(OptionDefinitionTest, empty) {
OptionDefinition opt_def("OPTION_RAPID_COMMIT", D6O_RAPID_COMMIT, "empty");
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Create option instance and provide empty buffer as expected.
OptionPtr option_v6;
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_RAPID_COMMIT, OptionBuffer())
);
ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
// Expect 'empty' DHCPv6 option.
@@ -266,31 +426,23 @@ TEST_F(OptionDefinitionTest, factoryEmpty) {
EXPECT_EQ(0, option_v6->getData().size());
// Repeat the same test scenario for DHCPv4 option.
- EXPECT_THROW(factory(Option::V4, 214, OptionBuffer(2)),isc::BadValue);
-
OptionPtr option_v4;
- ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, OptionBuffer()));
+ ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, OptionBuffer()));
// Expect 'empty' DHCPv4 option.
EXPECT_EQ(Option::V4, option_v4->getUniverse());
EXPECT_EQ(2, option_v4->getHeaderLen());
EXPECT_EQ(0, option_v4->getData().size());
-
- // This factory produces empty option (consisting of option type
- // and length). Attempt to provide some data in the buffer should
- // result in exception.
- EXPECT_THROW(factory(Option::V6, D6O_RAPID_COMMIT,OptionBuffer(2)),isc::BadValue);
}
-TEST_F(OptionDefinitionTest, factoryBinary) {
+// The purpose of this test is to verify that definition can be
+// creates for the option that holds binary data.
+TEST_F(OptionDefinitionTest, binary) {
// Binary option is the one that is represented by the generic
// Option class. In fact all options can be represented by this
// class but for some of them it is just natural. The SERVERID
// option consists of the option code, length and binary data so
// this one was picked for this test.
OptionDefinition opt_def("OPTION_SERVERID", D6O_SERVERID, "binary");
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Prepare some dummy data (serverid): 0, 1, 2 etc.
OptionBuffer buf(14);
@@ -302,7 +454,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
// object of the type Option should be returned.
OptionPtr option_v6;
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_SERVERID, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_SERVERID, buf);
);
// Expect base option type returned.
ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
@@ -320,7 +472,7 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
// Repeat the same test scenario for DHCPv4 option.
OptionPtr option_v4;
- ASSERT_NO_THROW(option_v4 = factory(Option::V4, 214, buf));
+ ASSERT_NO_THROW(option_v4 = opt_def.optionFactory(Option::V4, 214, buf));
// Expect 'empty' DHCPv4 option.
EXPECT_EQ(Option::V4, option_v4->getUniverse());
EXPECT_EQ(2, option_v4->getHeaderLen());
@@ -331,19 +483,19 @@ TEST_F(OptionDefinitionTest, factoryBinary) {
buf.begin()));
}
-TEST_F(OptionDefinitionTest, factoryIA6) {
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IA_NA option is used. This option comprises three uint32 fields.
+TEST_F(OptionDefinitionTest, recordIA6) {
// This option consists of IAID, T1 and T2 fields (each 4 bytes long).
const int option6_ia_len = 12;
// Get the factory function pointer.
- OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", true);
+ OptionDefinition opt_def("OPTION_IA_NA", D6O_IA_NA, "record", false);
// Each data field is uint32.
for (int i = 0; i < 3; ++i) {
EXPECT_NO_THROW(opt_def.addRecordField("uint32"));
}
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Check the positive scenario.
OptionBuffer buf(12);
@@ -351,7 +503,7 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
buf[i] = i;
}
OptionPtr option_v6;
- ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IA_NA, buf));
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IA_NA, buf));
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IA));
boost::shared_ptr<Option6IA> option_cast_v6 =
boost::static_pointer_cast<Option6IA>(option_v6);
@@ -359,25 +511,18 @@ TEST_F(OptionDefinitionTest, factoryIA6) {
EXPECT_EQ(0x04050607, option_cast_v6->getT1());
EXPECT_EQ(0x08090A0B, option_cast_v6->getT2());
- // This should work for DHCPv6 only, try passing invalid universe value.
- EXPECT_THROW(
- factory(Option::V4, D6O_IA_NA, OptionBuffer(option6_ia_len)),
- isc::BadValue
- );
- // The length of the buffer must be 12 bytes.
+ // The length of the buffer must be at least 12 bytes.
// Check too short buffer.
EXPECT_THROW(
- factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
- isc::OutOfRange
+ opt_def.optionFactory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len - 1)),
+ InvalidOptionValue
);
- // Check too long buffer.
- EXPECT_THROW(
- factory(Option::V6, D6O_IA_NA, OptionBuffer(option6_ia_len + 1)),
- isc::OutOfRange
- );
}
-TEST_F(OptionDefinitionTest, factoryIAAddr6) {
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used.
+TEST_F(OptionDefinitionTest, recordIAAddr6) {
// This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
// valid-lifetime fields (each 4 bytes long).
const int option6_iaaddr_len = 24;
@@ -386,24 +531,20 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
// Check the positive scenario.
OptionPtr option_v6;
asiolink::IOAddress addr_v6("2001:0db8::ff00:0042:8329");
OptionBuffer buf(asiolink::V6ADDRESS_LEN);
- ASSERT_TRUE(addr_v6.getAddress().is_v6());
- asio::ip::address_v6::bytes_type addr_bytes =
- addr_v6.getAddress().to_v6().to_bytes();
- ASSERT_EQ(asiolink::V6ADDRESS_LEN, addr_bytes.size());
- std::copy(addr_bytes.begin(), addr_bytes.end(), buf.begin());
+ ASSERT_TRUE(addr_v6.isV6());
+ const std::vector<uint8_t>& vec = addr_v6.toBytes();
+ ASSERT_EQ(asiolink::V6ADDRESS_LEN, vec.size());
+ std::copy(vec.begin(), vec.end(), buf.begin());
for (int i = 0; i < option6_iaaddr_len - asiolink::V6ADDRESS_LEN; ++i) {
buf.push_back(i);
}
- ASSERT_NO_THROW(option_v6 = factory(Option::V6, D6O_IAADDR, buf));
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR, buf));
ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
boost::shared_ptr<Option6IAAddr> option_cast_v6 =
boost::static_pointer_cast<Option6IAAddr>(option_v6);
@@ -411,71 +552,97 @@ TEST_F(OptionDefinitionTest, factoryIAAddr6) {
EXPECT_EQ(0x00010203, option_cast_v6->getPreferred());
EXPECT_EQ(0x04050607, option_cast_v6->getValid());
- // This should work for DHCPv6 only, try passing invalid universe value.
- EXPECT_THROW(
- factory(Option::V4, D6O_IAADDR, OptionBuffer(option6_iaaddr_len)),
- isc::BadValue
- );
- // The length of the buffer must be 12 bytes.
+ // The length of the buffer must be at least 12 bytes.
// Check too short buffer.
EXPECT_THROW(
- factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
- isc::OutOfRange
+ opt_def.optionFactory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len - 1)),
+ InvalidOptionValue
);
- // Check too long buffer.
- EXPECT_THROW(
- factory(Option::V6, D6O_IAADDR, OptionBuffer(option6_iaaddr_len + 1)),
- isc::OutOfRange
- );
}
-TEST_F(OptionDefinitionTest, factoryIntegerInvalidType) {
- // The template function factoryInteger<> accepts integer values only
- // as template typename. Here we try passing different type and
- // see if it rejects it.
- EXPECT_THROW(
- OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE, OptionBuffer(1)),
- isc::dhcp::InvalidDataType
- );
+// The purpose of this test is to verify that definition can be created
+// for option that comprises record of data. In this particular test
+// the IAADDR option is used. The data for the option is speicifed as
+// a vector of strings. Each string carries the data for the corresponding
+// data field.
+TEST_F(OptionDefinitionTest, recordIAAddr6Tokenized) {
+ // This option consists of IPV6 Address (16 bytes) and preferred-lifetime and
+ // valid-lifetime fields (each 4 bytes long).
+ OptionDefinition opt_def("OPTION_IAADDR", D6O_IAADDR, "record");
+ ASSERT_NO_THROW(opt_def.addRecordField("ipv6-address"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+ ASSERT_NO_THROW(opt_def.addRecordField("uint32"));
+
+ // Check the positive scenario.
+ std::vector<std::string> data_field_values;
+ data_field_values.push_back("2001:0db8::ff00:0042:8329");
+ data_field_values.push_back("1234");
+ data_field_values.push_back("5678");
+
+ OptionPtr option_v6;
+ ASSERT_NO_THROW(option_v6 = opt_def.optionFactory(Option::V6, D6O_IAADDR,
+ data_field_values));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IAAddr));
+ boost::shared_ptr<Option6IAAddr> option_cast_v6 =
+ boost::static_pointer_cast<Option6IAAddr>(option_v6);
+ EXPECT_EQ("2001:db8::ff00:42:8329", option_cast_v6->getAddress().toText());
+ EXPECT_EQ(1234, option_cast_v6->getPreferred());
+ EXPECT_EQ(5678, option_cast_v6->getValid());
}
-TEST_F(OptionDefinitionTest, factoryUint8) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8) {
OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
OptionPtr option_v6;
// Try to use correct buffer length = 1 byte.
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer(1, 1));
);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint8_t>));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint8_t>));
// Validate the value.
- boost::shared_ptr<Option6Int<uint8_t> > option_cast_v6 =
- boost::static_pointer_cast<Option6Int<uint8_t> >(option_v6);
+ boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6);
EXPECT_EQ(1, option_cast_v6->getValue());
- // Try to provide too large buffer. Expect exception.
+ // Try to provide zero-length buffer. Expect exception.
EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer(3)),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
+ InvalidOptionValue
);
- // Try to provide zero-length buffer. Expect exception.
- EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_PREFERENCE, OptionBuffer()),
- isc::OutOfRange
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint8 value can be created and that this definition
+// can be used to create an option with single uint8 value.
+TEST_F(OptionDefinitionTest, uint8Tokenized) {
+ OptionDefinition opt_def("OPTION_PREFERENCE", D6O_PREFERENCE, "uint8");
+
+ OptionPtr option_v6;
+ std::vector<std::string> values;
+ values.push_back("123");
+ values.push_back("456");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_PREFERENCE, values);
);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint8_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint8_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint8_t> >(option_v6);
+ EXPECT_EQ(123, option_cast_v6->getValue());
// @todo Add more cases for DHCPv4
}
-TEST_F(OptionDefinitionTest, factoryUint16) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16) {
OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
OptionPtr option_v6;
// Try to use correct buffer length = 2 bytes.
@@ -483,33 +650,52 @@ TEST_F(OptionDefinitionTest, factoryUint16) {
buf.push_back(1);
buf.push_back(2);
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, buf);
);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint16_t>));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint16_t>));
// Validate the value.
- boost::shared_ptr<Option6Int<uint16_t> > option_cast_v6 =
- boost::static_pointer_cast<Option6Int<uint16_t> >(option_v6);
+ boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6);
EXPECT_EQ(0x0102, option_cast_v6->getValue());
- // Try to provide too large buffer. Expect exception.
- EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(3)),
- isc::OutOfRange
- );
// Try to provide zero-length buffer. Expect exception.
EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, OptionBuffer(1)),
+ InvalidOptionValue
);
// @todo Add more cases for DHCPv4
}
-TEST_F(OptionDefinitionTest, factoryUint32) {
+// The purpose of this test is to verify that definition for option that
+// comprises single uint16 value can be created and that this definition
+// can be used to create an option with single uint16 value.
+TEST_F(OptionDefinitionTest, uint16Tokenized) {
+ OptionDefinition opt_def("OPTION_ELAPSED_TIME", D6O_ELAPSED_TIME, "uint16");
+
+ OptionPtr option_v6;
+
+ std::vector<std::string> values;
+ values.push_back("1234");
+ values.push_back("5678");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_ELAPSED_TIME, values);
+ );
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint16_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint16_t> >(option_v6);
+ EXPECT_EQ(1234, option_cast_v6->getValue());
+
+ // @todo Add more cases for DHCPv4
+
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32) {
OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
OptionPtr option_v6;
OptionBuffer buf;
@@ -518,35 +704,52 @@ TEST_F(OptionDefinitionTest, factoryUint32) {
buf.push_back(3);
buf.push_back(4);
ASSERT_NO_THROW(
- option_v6 = factory(Option::V6, D6O_CLT_TIME, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, buf);
);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option6Int<uint32_t>));
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint32_t>));
// Validate the value.
- boost::shared_ptr<Option6Int<uint32_t> > option_cast_v6 =
- boost::static_pointer_cast<Option6Int<uint32_t> >(option_v6);
+ boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6);
EXPECT_EQ(0x01020304, option_cast_v6->getValue());
- // Try to provide too large buffer. Expect exception.
+ // Try to provide too short buffer. Expect exception.
EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(5)),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
+ InvalidOptionValue
);
- // Try to provide zero-length buffer. Expect exception.
- EXPECT_THROW(
- option_v6 = factory(Option::V6, D6O_CLT_TIME, OptionBuffer(2)),
- isc::OutOfRange
+
+ // @todo Add more cases for DHCPv4
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises single uint32 value can be created and that this definition
+// can be used to create an option with single uint32 value.
+TEST_F(OptionDefinitionTest, uint32Tokenized) {
+ OptionDefinition opt_def("OPTION_CLT_TIME", D6O_CLT_TIME, "uint32");
+
+ OptionPtr option_v6;
+ std::vector<std::string> values;
+ values.push_back("123456");
+ values.push_back("789");
+ ASSERT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, D6O_CLT_TIME, values);
);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionInt<uint32_t>));
+ // Validate the value.
+ boost::shared_ptr<OptionInt<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionInt<uint32_t> >(option_v6);
+ EXPECT_EQ(123456, option_cast_v6->getValue());
// @todo Add more cases for DHCPv4
}
-TEST_F(OptionDefinitionTest, factoryUint16Array) {
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16Array) {
// Let's define some dummy option.
const uint16_t opt_code = 79;
OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
OptionPtr option_v6;
// Positive scenario, initiate the buffer with length being
@@ -558,11 +761,11 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
}
// Constructor should succeed because buffer has correct size.
EXPECT_NO_THROW(
- option_v6 = factory(Option::V6, opt_code, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint16_t>));
- boost::shared_ptr<Option6IntArray<uint16_t> > option_cast_v6 =
- boost::static_pointer_cast<Option6IntArray<uint16_t> >(option_v6);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionIntArray<uint16_t>));
+ boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6);
// Get the values from the initiated options and validate.
std::vector<uint16_t> values = option_cast_v6->getValues();
for (int i = 0; i < values.size(); ++i) {
@@ -576,24 +779,50 @@ TEST_F(OptionDefinitionTest, factoryUint16Array) {
// Provided buffer size must be greater than zero. Check if we
// get exception if we provide zero-length buffer.
EXPECT_THROW(
- option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+ InvalidOptionValue
);
// Buffer length must be multiple of data type size.
EXPECT_THROW(
- option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+ InvalidOptionValue
);
}
-TEST_F(OptionDefinitionTest, factoryUint32Array) {
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint16 values can be created and that this definition
+// can be used to create option with an array of uint16 values.
+TEST_F(OptionDefinitionTest, uint16ArrayTokenized) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 79;
+ OptionDefinition opt_def("OPTION_UINT16_ARRAY", opt_code, "uint16", true);
+
+ OptionPtr option_v6;
+ std::vector<std::string> str_values;
+ str_values.push_back("12345");
+ str_values.push_back("5679");
+ str_values.push_back("12");
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
+ );
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionIntArray<uint16_t>));
+ boost::shared_ptr<OptionIntArray<uint16_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint16_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint16_t> values = option_cast_v6->getValues();
+ EXPECT_EQ(12345, values[0]);
+ EXPECT_EQ(5679, values[1]);
+ EXPECT_EQ(12, values[2]);
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32Array) {
// Let's define some dummy option.
const uint16_t opt_code = 80;
OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
- Option::Factory* factory(NULL);
- EXPECT_NO_THROW(factory = opt_def.getFactory());
- ASSERT_TRUE(factory != NULL);
OptionPtr option_v6;
// Positive scenario, initiate the buffer with length being
@@ -605,11 +834,11 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
}
// Constructor should succeed because buffer has correct size.
EXPECT_NO_THROW(
- option_v6 = factory(Option::V6, opt_code, buf);
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, buf);
);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option6IntArray<uint32_t>));
- boost::shared_ptr<Option6IntArray<uint32_t> > option_cast_v6 =
- boost::static_pointer_cast<Option6IntArray<uint32_t> >(option_v6);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionIntArray<uint32_t>));
+ boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6);
// Get the values from the initiated options and validate.
std::vector<uint32_t> values = option_cast_v6->getValues();
for (int i = 0; i < values.size(); ++i) {
@@ -623,16 +852,85 @@ TEST_F(OptionDefinitionTest, factoryUint32Array) {
// Provided buffer size must be greater than zero. Check if we
// get exception if we provide zero-length buffer.
EXPECT_THROW(
- option_v6 = factory(Option::V6, opt_code, OptionBuffer()),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer()),
+ InvalidOptionValue
);
// Buffer length must be multiple of data type size.
EXPECT_THROW(
- option_v6 = factory(Option::V6, opt_code, OptionBuffer(5)),
- isc::OutOfRange
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, OptionBuffer(5)),
+ InvalidOptionValue
+ );
+}
+
+// The purpose of this test is to verify that definition for option that
+// comprises array of uint32 values can be created and that this definition
+// can be used to create option with an array of uint32 values.
+TEST_F(OptionDefinitionTest, uint32ArrayTokenized) {
+ // Let's define some dummy option.
+ const uint16_t opt_code = 80;
+
+ OptionDefinition opt_def("OPTION_UINT32_ARRAY", opt_code, "uint32", true);
+
+ OptionPtr option_v6;
+ std::vector<std::string> str_values;
+ str_values.push_back("123456");
+ str_values.push_back("7");
+ str_values.push_back("256");
+ str_values.push_back("1111");
+
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, str_values);
+ );
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionIntArray<uint32_t>));
+ boost::shared_ptr<OptionIntArray<uint32_t> > option_cast_v6 =
+ boost::static_pointer_cast<OptionIntArray<uint32_t> >(option_v6);
+ // Get the values from the initiated options and validate.
+ std::vector<uint32_t> values = option_cast_v6->getValues();
+ EXPECT_EQ(123456, values[0]);
+ EXPECT_EQ(7, values[1]);
+ EXPECT_EQ(256, values[2]);
+ EXPECT_EQ(1111, values[3]);
+}
+
+// The purpose of this test is to verify that the definition can be created
+// for the option that comprises string value in the UTF8 format.
+TEST_F(OptionDefinitionTest, utf8StringTokenized) {
+ // Let's create some dummy option.
+ const uint16_t opt_code = 80;
+ OptionDefinition opt_def("OPTION_WITH_STRING", opt_code, "string");
+
+ std::vector<std::string> values;
+ values.push_back("Hello World");
+ values.push_back("this string should not be included in the option");
+ OptionPtr option_v6;
+ EXPECT_NO_THROW(
+ option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
+ );
+ ASSERT_TRUE(option_v6);
+ ASSERT_TRUE(typeid(*option_v6) == typeid(OptionCustom));
+ std::vector<uint8_t> data = option_v6->getData();
+ std::vector<uint8_t> ref_data(values[0].c_str(), values[0].c_str()
+ + values[0].length());
+ EXPECT_TRUE(std::equal(ref_data.begin(), ref_data.end(), data.begin()));
+}
+
+// The purpose of this test is to check that non-integer data type can't
+// be used for factoryInteger function.
+TEST_F(OptionDefinitionTest, integerInvalidType) {
+ // The template function factoryInteger<> accepts integer values only
+ // as template typename. Here we try passing different type and
+ // see if it rejects it.
+ OptionBuffer buf(1);
+ EXPECT_THROW(
+ OptionDefinition::factoryInteger<bool>(Option::V6, D6O_PREFERENCE,
+ buf.begin(), buf.end()),
+ isc::dhcp::InvalidDataType
);
}
+// The purpose of this test is to verify that helper methods
+// haveIA6Format and haveIAAddr6Format can be used to determine
+// IA_NA and IAADDR option formats.
TEST_F(OptionDefinitionTest, recognizeFormat) {
// IA_NA option format.
OptionDefinition opt_def1("OPTION_IA_NA", D6O_IA_NA, "record");
diff --git a/src/lib/dhcp/tests/option_int_array_unittest.cc b/src/lib/dhcp/tests/option_int_array_unittest.cc
new file mode 100644
index 0000000..1aeb584
--- /dev/null
+++ b/src/lib/dhcp/tests/option_int_array_unittest.cc
@@ -0,0 +1,488 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int_array.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// @brief OptionIntArray test class.
+class OptionIntArrayTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the option buffer with some data.
+ OptionIntArrayTest(): buf_(255), out_buf_(255) {
+ for (int i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief Test parsing buffer into array of int8_t or uint8_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int8_t or uint8_t type is used.
+ ///
+ /// @param u universe (v4 or V6).
+ /// @tparam T int8_t or uint8_t.
+ template<typename T>
+ void bufferToIntTest8(const Option::Universe u) {
+ // Create option that conveys array of multiple uint8_t or int8_t values.
+ // In fact there is no need to use this template class for array
+ // of uint8_t values because Option class is sufficient - it
+ // returns the buffer which is actually the array of uint8_t.
+ // However, since we allow using uint8_t types with this template
+ // class we have to test it here.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 10;
+ const uint16_t opt_code = 80;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Provided buffer is not empty so it should not throw exception.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return the collection of int8_t or uint8_t values that
+ // we can match with the buffer we used to create the option.
+ std::vector<T> values = opt->getValues();
+ // We need to copy values from the buffer to apply sign if signed
+ // type is used.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; ++i) {
+ // Values have been read from the buffer in network
+ // byte order. We put them back in the same order here.
+ reference_values.push_back(static_cast<T>(buf_[i]));
+ }
+
+ // Compare the values against the reference buffer.
+ ASSERT_EQ(opt_len, values.size());
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.begin()
+ + opt_len, values.begin()));
+
+ // test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 10 bytes.
+ EXPECT_EQ(10, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 10 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(12, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(10, out.readUint8());
+ } else {
+ // The total length is 10 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(14, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(10, out.readUint16());
+ }
+
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+ /// @brief Test parsing buffer into array of int16_t or uint16_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int16_t or uint16_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int16_t or uint16_t.
+ template<typename T>
+ void bufferToIntTest16(const Option::Universe u) {
+ // Create option that conveys array of multiple uint16_t or int16_t values.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 20;
+ const uint16_t opt_code = 81;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Constructor throws exception if provided buffer's length is not
+ // multiple of 2-bytes.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 5),
+ isc::OutOfRange
+ );
+
+ // Now the buffer length is correct.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return vector of uint16_t values which should be
+ // constructed from the buffer we provided.
+ std::vector<T> values = opt->getValues();
+ ASSERT_EQ(opt_len, values.size() * sizeof(T));
+ // Create reference values from the buffer so as we can
+ // simply compare two vectors.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; i += 2) {
+ reference_values.push_back((buf_[i] << 8) |
+ buf_[i + 1]);
+ }
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
+ values.begin()));
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 20 bytes.
+ EXPECT_EQ(20, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 20 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(22, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(20, out.readUint8());
+ } else {
+ // The total length is 20 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(24, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(20, out.readUint16());
+ }
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+ /// @brief Test parsing buffer into array of int32_t or uint32_t values.
+ ///
+ /// @warning this function does not perform type check. Make
+ /// sure that only int32_t or uint32_t type is used.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @tparam T int32_t or uint32_t.
+ template<typename T>
+ void bufferToIntTest32(const Option::Universe u) {
+ // Create option that conveys array of multiple uint16_t values.
+ boost::shared_ptr<OptionIntArray<T> > opt;
+ const int opt_len = 40;
+ const uint16_t opt_code = 82;
+
+ // Constructor throws exception if provided buffer is empty.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin()),
+ isc::OutOfRange
+ );
+
+ // Constructor throws exception if provided buffer's length is not
+ // multiple of 4-bytes.
+ EXPECT_THROW(
+ OptionIntArray<T>(u, opt_code, buf_.begin(), buf_.begin() + 9),
+ isc::OutOfRange
+ );
+
+ // Now the buffer length is correct.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionIntArray<T> >(new OptionIntArray<T>(u, opt_code, buf_.begin(),
+ buf_.begin() + opt_len))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ // Option should return vector of uint32_t values which should be
+ // constructed from the buffer we provided.
+ std::vector<T> values = opt->getValues();
+ ASSERT_EQ(opt_len, values.size() * sizeof(T));
+ // Create reference values from the buffer so as we can
+ // simply compare two vectors.
+ std::vector<T> reference_values;
+ for (int i = 0; i < opt_len; i += 4) {
+ reference_values.push_back((buf_[i] << 24) |
+ (buf_[i + 1] << 16 & 0x00FF0000) |
+ (buf_[i + 2] << 8 & 0xFF00) |
+ (buf_[i + 3] & 0xFF));
+ }
+ EXPECT_TRUE(std::equal(reference_values.begin(), reference_values.end(),
+ values.begin()));
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 40 bytes.
+ EXPECT_EQ(40, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(opt_code, opt->getType());
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+
+ if (u == Option::V4) {
+ // The total length is 40 bytes for data and 2 bytes for a header.
+ ASSERT_EQ(42, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(40, out.readUint8());
+ } else {
+ // The total length is 40 bytes for data and 4 bytes for a header.
+ ASSERT_EQ(44, out_buf_.getLength());
+ // if option type is correct
+ EXPECT_EQ(opt_code, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(40, out.readUint16());
+ }
+
+ // if data is correct
+ std::vector<uint8_t> out_data;
+ ASSERT_NO_THROW(out.readVector(out_data, opt_len));
+ ASSERT_EQ(opt_len, out_data.size());
+ EXPECT_TRUE(std::equal(buf_.begin(), buf_.begin() + opt_len, out_data.begin()));;
+ }
+
+
+ OptionBuffer buf_; ///< Option buffer
+ OutputBuffer out_buf_; ///< Output buffer
+};
+
+/// @todo: below, there is a bunch of tests for options that
+/// convey unsigned values. We should maybe extend these tests for
+/// signed types too.
+
+TEST_F(OptionIntArrayTest, useInvalidType) {
+ const uint16_t opt_code = 80;
+ EXPECT_THROW(
+ boost::scoped_ptr<
+ OptionIntArray<bool> >(new OptionIntArray<bool>(Option::V6, opt_code,
+ OptionBuffer(5))),
+ InvalidDataType
+ );
+
+ EXPECT_THROW(
+ boost::scoped_ptr<
+ OptionIntArray<int64_t> >(new OptionIntArray<int64_t>(Option::V6,
+ opt_code,
+ OptionBuffer(10))),
+ InvalidDataType
+ );
+
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint8V4) {
+ bufferToIntTest8<uint8_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint8V6) {
+ bufferToIntTest8<uint8_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt8V4) {
+ bufferToIntTest8<int8_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt8V6) {
+ bufferToIntTest8<int8_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint16V4) {
+ bufferToIntTest16<uint16_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint16V6) {
+ bufferToIntTest16<uint16_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt16V4) {
+ bufferToIntTest16<int16_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt16V6) {
+ bufferToIntTest16<int16_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint32V4) {
+ bufferToIntTest32<uint32_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToUint32V6) {
+ bufferToIntTest32<uint32_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt32V4) {
+ bufferToIntTest32<int32_t>(Option::V4);
+}
+
+TEST_F(OptionIntArrayTest, bufferToInt32V6) {
+ bufferToIntTest32<int32_t>(Option::V6);
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint8) {
+ const uint16_t opt_code = 100;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<uint8_t> >
+ opt(new OptionIntArray<uint8_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<uint8_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<uint8_t>::max() - i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<uint8_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt8) {
+ const uint16_t opt_code = 100;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<int8_t> >
+ opt(new OptionIntArray<int8_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<int8_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<int8_t>::min() + i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<int8_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint16) {
+ const uint16_t opt_code = 101;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<uint16_t> >
+ opt(new OptionIntArray<uint16_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<uint16_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<uint16_t>::max() - i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<uint16_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt16) {
+ const uint16_t opt_code = 101;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<int16_t> >
+ opt(new OptionIntArray<int16_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<int16_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<int16_t>::min() + i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<int16_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+TEST_F(OptionIntArrayTest, setValuesUint32) {
+ const uint32_t opt_code = 101;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<uint32_t> >
+ opt(new OptionIntArray<uint32_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<uint32_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<uint32_t>::max() - i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<uint32_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+TEST_F(OptionIntArrayTest, setValuesInt32) {
+ const uint32_t opt_code = 101;
+ // Create option with empty vector of values.
+ boost::shared_ptr<OptionIntArray<int32_t> >
+ opt(new OptionIntArray<int32_t>(Option::V6, opt_code));
+ // Initialize vector with some data and pass to the option.
+ std::vector<int32_t> values;
+ for (int i = 0; i < 10; ++i) {
+ values.push_back(numeric_limits<int32_t>::min() + i);
+ }
+ opt->setValues(values);
+
+ // Check if universe, option type and data was set correctly.
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(opt_code, opt->getType());
+ std::vector<int32_t> returned_values = opt->getValues();
+ EXPECT_TRUE(std::equal(values.begin(), values.end(), returned_values.begin()));
+}
+
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_int_unittest.cc b/src/lib/dhcp/tests/option_int_unittest.cc
new file mode 100644
index 0000000..81ebcf0
--- /dev/null
+++ b/src/lib/dhcp/tests/option_int_unittest.cc
@@ -0,0 +1,563 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcp/dhcp6.h>
+#include <dhcp/option.h>
+#include <dhcp/option6_iaaddr.h>
+#include <dhcp/option_int.h>
+#include <util/buffer.h>
+
+#include <boost/pointer_cast.hpp>
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+using namespace isc::util;
+
+namespace {
+
+/// Option code being used in many test cases.
+const uint16_t TEST_OPT_CODE = 232;
+
+/// @brief OptionInt test class.
+class OptionIntTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ ///
+ /// Initializes the option buffer with some data.
+ OptionIntTest(): buf_(255), out_buf_(255) {
+ for (int i = 0; i < 255; i++) {
+ buf_[i] = 255 - i;
+ }
+ }
+
+ /// @brief Basic test for int8 and uint8 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int8_t or uint8_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int8_t or uint8_t.
+ template<typename T>
+ void basicTest8(const Option::Universe u) {
+ // Create option that conveys single 8 bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with this value.
+ buf_[0] = 0xa1;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 1))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the same value that we initialized the first
+ // byte of the buffer with.
+ EXPECT_EQ(static_cast<T>(0xa1), opt->getValue());
+
+ // test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 1 byte.
+ EXPECT_EQ(1, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 1 byte for data and 2 bytes or 4 bytes
+ // for option code and option length.
+ if (u == Option::V4) {
+ EXPECT_EQ(3, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(5, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(1, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(1, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1, out.readUint8() );
+ }
+
+ /// @brief Basic test for int16 and uint16 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int16_t or uint16_t type is used.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @tparam T int16_t or uint16_t.
+ template<typename T>
+ void basicTest16(const Option::Universe u) {
+ // Create option that conveys single 16-bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with uint16_t value.
+ buf_[0] = 0xa1;
+ buf_[1] = 0xa2;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 2))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the value equal to the contents of first
+ // and second byte of the buffer.
+ EXPECT_EQ(static_cast<T>(0xa1a2), opt->getValue());
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 2 bytes.
+ EXPECT_EQ(2, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 2 bytes for data and 2 or 4 bytes for aheader.
+ if (u == Option::V4) {
+ EXPECT_EQ(4, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(6, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(2, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(2, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1a2, out.readUint16() );
+ }
+
+ /// @brief Basic test for int32 and uint32 types.
+ ///
+ /// @note this function does not perform type check. Make
+ /// sure that only int32_t or uint32_t type is used.
+ ///
+ /// @param u universe (V4 or V6).
+ /// @tparam T int32_t or uint32_t.
+ template<typename T>
+ void basicTest32(const Option::Universe u) {
+ // Create option that conveys single 32-bit integer value.
+ boost::shared_ptr<OptionInt<T> > opt;
+ // Initialize buffer with 32-bit integer value.
+ buf_[0] = 0xa1;
+ buf_[1] = 0xa2;
+ buf_[2] = 0xa3;
+ buf_[3] = 0xa4;
+ // Constructor may throw in case provided buffer is too short.
+ ASSERT_NO_THROW(
+ opt = boost::shared_ptr<OptionInt<T> >(new OptionInt<T>(u,
+ TEST_OPT_CODE,
+ buf_.begin(),
+ buf_.begin() + 4))
+ );
+
+ EXPECT_EQ(u, opt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // Option should return the value equal to the value made of
+ // first 4 bytes of the buffer.
+ EXPECT_EQ(static_cast<T>(0xa1a2a3a4), opt->getValue());
+
+ // Test for pack()
+ opt->pack(out_buf_);
+
+ // Data length is 4 bytes.
+ EXPECT_EQ(4, opt->len() - opt->getHeaderLen());
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ // The total length is 4 bytes for data and 2 or 4 bytes for a header.
+ if (u == Option::V4) {
+ EXPECT_EQ(6, out_buf_.getLength());
+ } else {
+ EXPECT_EQ(8, out_buf_.getLength());
+ }
+
+ // Check if pack worked properly:
+ InputBuffer out(out_buf_.getData(), out_buf_.getLength());
+ if (u == Option::V4) {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint8());
+ // if option length is correct
+ EXPECT_EQ(4, out.readUint8());
+ } else {
+ // if option type is correct
+ EXPECT_EQ(TEST_OPT_CODE, out.readUint16());
+ // if option length is correct
+ EXPECT_EQ(4, out.readUint16());
+ }
+ // if data is correct
+ EXPECT_EQ(0xa1a2a3a4, out.readUint32());
+ }
+
+ OptionBuffer buf_; ///< Option buffer
+ OutputBuffer out_buf_; ///< Output buffer
+};
+
+/// @todo: below, there is a bunch of tests for options that
+/// convey unsigned value. We should maybe extend these tests for
+/// signed types too.
+
+TEST_F(OptionIntTest, useInvalidType) {
+ EXPECT_THROW(
+ boost::scoped_ptr<OptionInt<bool> >(new OptionInt<bool>(Option::V6,
+ D6O_ELAPSED_TIME, 10)),
+ InvalidDataType
+ );
+
+ EXPECT_THROW(
+ boost::scoped_ptr<OptionInt<int64_t> >(new OptionInt<int64_t>(Option::V6,
+ D6O_ELAPSED_TIME, 10)),
+ InvalidDataType
+ );
+
+}
+
+TEST_F(OptionIntTest, basicUint8V4) {
+ basicTest8<uint8_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint8V6) {
+ basicTest8<uint8_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicUint16V4) {
+ basicTest16<uint16_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint16V6) {
+ basicTest16<uint16_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicUint32V4) {
+ basicTest32<uint32_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicUint32V6) {
+ basicTest32<uint32_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt8V4) {
+ basicTest8<int8_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt8V6) {
+ basicTest8<int8_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt16V4) {
+ basicTest16<int16_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt16V6) {
+ basicTest16<int16_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, basicInt32V4) {
+ basicTest32<int32_t>(Option::V4);
+}
+
+TEST_F(OptionIntTest, basicInt32V6) {
+ basicTest32<int32_t>(Option::V6);
+}
+
+TEST_F(OptionIntTest, setValueUint8) {
+ boost::shared_ptr<OptionInt<uint8_t> > opt(new OptionInt<uint8_t>(Option::V6,
+ D6O_PREFERENCE, 123));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(111);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_PREFERENCE, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(111, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt8) {
+ boost::shared_ptr<OptionInt<int8_t> > opt(new OptionInt<int8_t>(Option::V6,
+ D6O_PREFERENCE, -123));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(-123, opt->getValue());
+ // Override the value.
+ opt->setValue(-111);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_PREFERENCE, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(-111, opt->getValue());
+}
+
+
+TEST_F(OptionIntTest, setValueUint16) {
+ boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V6,
+ D6O_ELAPSED_TIME, 123));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(0x0102);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(0x0102, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt16) {
+ boost::shared_ptr<OptionInt<int16_t> > opt(new OptionInt<int16_t>(Option::V6,
+ D6O_ELAPSED_TIME, -16500));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(-16500, opt->getValue());
+ // Override the value.
+ opt->setValue(-20100);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_ELAPSED_TIME, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(-20100, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueUint32) {
+ boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
+ D6O_CLT_TIME, 123));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(123, opt->getValue());
+ // Override the value.
+ opt->setValue(0x01020304);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_CLT_TIME, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(0x01020304, opt->getValue());
+}
+
+TEST_F(OptionIntTest, setValueInt32) {
+ boost::shared_ptr<OptionInt<int32_t> > opt(new OptionInt<int32_t>(Option::V6,
+ D6O_CLT_TIME, -120100));
+ // Check if constructor intitialized the option value correctly.
+ EXPECT_EQ(-120100, opt->getValue());
+ // Override the value.
+ opt->setValue(-125000);
+
+ EXPECT_EQ(Option::V6, opt->getUniverse());
+ EXPECT_EQ(D6O_CLT_TIME, opt->getType());
+ // Check if the value has been overriden.
+ EXPECT_EQ(-125000, opt->getValue());
+}
+
+TEST_F(OptionIntTest, packSuboptions4) {
+ boost::shared_ptr<OptionInt<uint16_t> > opt(new OptionInt<uint16_t>(Option::V4,
+ TEST_OPT_CODE,
+ 0x0102));
+ // Add sub option with some 4 bytes of data (each byte set to 1)
+ OptionPtr sub1(new Option(Option::V4, TEST_OPT_CODE + 1, OptionBuffer(4, 1)));
+ // Add sub option with some 5 bytes of data (each byte set to 2)
+ OptionPtr sub2(new Option(Option::V4, TEST_OPT_CODE + 2, OptionBuffer(5, 2)));
+
+ // Add suboptions.
+ opt->addOption(sub1);
+ opt->addOption(sub2);
+
+ // Prepare reference data: option + suoptions in wire format.
+ uint8_t expected[] = {
+ TEST_OPT_CODE, 15, // option header
+ 0x01, 0x02, // data, uint16_t value = 0x0102
+ TEST_OPT_CODE + 1, 0x04, 0x01, 0x01, 0x01, 0x01, // sub1
+ TEST_OPT_CODE + 2, 0x05, 0x02, 0x02, 0x02, 0x02, 0x02 // sub2
+ };
+
+ // Create on-wire format of option and suboptions.
+ opt->pack(out_buf_);
+ // Compare the on-wire data with the reference buffer.
+ ASSERT_EQ(sizeof(expected), out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, sizeof(expected)));
+}
+
+TEST_F(OptionIntTest, packSuboptions6) {
+ // option code is really uint16_t, but using uint8_t
+ // for easier conversion to uint8_t array.
+ uint8_t opt_code = 80;
+
+ boost::shared_ptr<OptionInt<uint32_t> > opt(new OptionInt<uint32_t>(Option::V6,
+ opt_code, 0x01020304));
+ OptionPtr sub1(new Option(Option::V6, 0xcafe));
+
+ boost::shared_ptr<Option6IAAddr> addr1(
+ new Option6IAAddr(D6O_IAADDR, IOAddress("2001:db8:1234:5678::abcd"), 0x5000, 0x7000));
+
+ opt->addOption(sub1);
+ opt->addOption(addr1);
+
+ ASSERT_EQ(28, addr1->len());
+ ASSERT_EQ(4, sub1->len());
+ ASSERT_EQ(40, opt->len());
+
+ uint8_t expected[] = {
+ 0, opt_code, // type
+ 0, 36, // length
+ 0x01, 0x02, 0x03, 0x04, // uint32_t value
+
+ // iaaddr suboption
+ D6O_IAADDR / 256, D6O_IAADDR % 256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+
+ // Create on-wire format of option and suboptions.
+ opt->pack(out_buf_);
+ // Compare the on-wire data with the reference buffer.
+ ASSERT_EQ(40, out_buf_.getLength());
+ EXPECT_EQ(0, memcmp(out_buf_.getData(), expected, 40));
+}
+
+TEST_F(OptionIntTest, unpackSuboptions4) {
+ // Prepare reference data.
+ const uint8_t expected[] = {
+ TEST_OPT_CODE, 0x0A, // option code and length
+ 0x01, 0x02, 0x03, 0x04, // data, uint32_t value = 0x01020304
+ TEST_OPT_CODE + 1, 0x4, 0x01, 0x01, 0x01, 0x01 // suboption
+ };
+ // Make sure that the buffer size is sufficient to copy the
+ // elements from the array.
+ ASSERT_GE(buf_.size(), sizeof(expected));
+ // Copy the data to a vector so as we can pass it to the
+ // OptionInt's constructor.
+ memcpy(&buf_[0], expected, sizeof(expected));
+
+ // Create an option.
+ boost::shared_ptr<OptionInt<uint32_t> > opt;
+ EXPECT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionInt<uint32_t> >(new OptionInt<uint32_t>(Option::V4, TEST_OPT_CODE,
+ buf_.begin() + 2,
+ buf_.begin() + sizeof(expected)));
+ );
+ ASSERT_TRUE(opt);
+
+ // Verify that it has expected type and data.
+ EXPECT_EQ(TEST_OPT_CODE, opt->getType());
+ EXPECT_EQ(0x01020304, opt->getValue());
+
+ // Expect that there is the sub option with the particular
+ // option code added.
+ OptionPtr subopt = opt->getOption(TEST_OPT_CODE + 1);
+ ASSERT_TRUE(subopt);
+ // Check that this option has correct universe and code.
+ EXPECT_EQ(Option::V4, subopt->getUniverse());
+ EXPECT_EQ(TEST_OPT_CODE + 1, subopt->getType());
+ // Check the sub option's data.
+ OptionBuffer subopt_buf = subopt->getData();
+ ASSERT_EQ(4, subopt_buf.size());
+ // The data in the input buffer starts at offset 8.
+ EXPECT_TRUE(std::equal(subopt_buf.begin(), subopt_buf.end(), buf_.begin() + 8));
+}
+
+TEST_F(OptionIntTest, unpackSuboptions6) {
+ // option code is really uint16_t, but using uint8_t
+ // for easier conversion to uint8_t array.
+ const uint8_t opt_code = 80;
+ // Prepare reference data.
+ uint8_t expected[] = {
+ 0, opt_code, // type
+ 0, 34, // length
+ 0x01, 0x02, // uint16_t value
+
+ // iaaddr suboption
+ D6O_IAADDR / 256, D6O_IAADDR % 256, // type
+ 0, 24, // len
+ 0x20, 0x01, 0xd, 0xb8, 0x12,0x34, 0x56, 0x78,
+ 0, 0, 0, 0, 0, 0, 0xab, 0xcd, // IP address
+ 0, 0, 0x50, 0, // preferred-lifetime
+ 0, 0, 0x70, 0, // valid-lifetime
+
+ // suboption
+ 0xca, 0xfe, // type
+ 0, 0 // len
+ };
+ ASSERT_EQ(38, sizeof(expected));
+
+ // Make sure that the buffer's size is sufficient to
+ // copy the elements from the array.
+ ASSERT_GE(buf_.size(), sizeof(expected));
+ memcpy(&buf_[0], expected, sizeof(expected));
+
+ boost::shared_ptr<OptionInt<uint16_t> > opt;
+ EXPECT_NO_THROW(
+ opt = boost::shared_ptr<
+ OptionInt<uint16_t> >(new OptionInt<uint16_t>(Option::V6, opt_code,
+ buf_.begin() + 4,
+ buf_.begin() + sizeof(expected)));
+ );
+ ASSERT_TRUE(opt);
+
+ EXPECT_EQ(opt_code, opt->getType());
+ EXPECT_EQ(0x0102, opt->getValue());
+
+ // Checks for address option
+ OptionPtr subopt = opt->getOption(D6O_IAADDR);
+ ASSERT_TRUE(subopt);
+ boost::shared_ptr<Option6IAAddr> addr(boost::dynamic_pointer_cast<Option6IAAddr>(subopt));
+ ASSERT_TRUE(addr);
+
+ EXPECT_EQ(D6O_IAADDR, addr->getType());
+ EXPECT_EQ(28, addr->len());
+ EXPECT_EQ(0x5000, addr->getPreferred());
+ EXPECT_EQ(0x7000, addr->getValid());
+ EXPECT_EQ("2001:db8:1234:5678::abcd", addr->getAddress().toText());
+
+ // Checks for dummy option
+ subopt = opt->getOption(0xcafe);
+ ASSERT_TRUE(subopt); // should be non-NULL
+
+ EXPECT_EQ(0xcafe, subopt->getType());
+ EXPECT_EQ(4, subopt->len());
+ // There should be no data at all
+ EXPECT_EQ(0, subopt->getData().size());
+
+ // Try to get non-existent option.
+ subopt = opt->getOption(1);
+ // Expecting NULL which means that option does not exist.
+ ASSERT_FALSE(subopt);
+}
+
+} // anonymous namespace
diff --git a/src/lib/dhcp/tests/option_unittest.cc b/src/lib/dhcp/tests/option_unittest.cc
index 50d143f..afa64d5 100644
--- a/src/lib/dhcp/tests/option_unittest.cc
+++ b/src/lib/dhcp/tests/option_unittest.cc
@@ -524,4 +524,27 @@ TEST_F(OptionTest, setData) {
EXPECT_TRUE(0 == memcmp(&buf_[0], test_data + opt1->getHeaderLen(),
buf_.size()));
}
+
+// This test verifies that options can be compared using equal() method.
+TEST_F(OptionTest, equal) {
+
+ // five options with varying lengths
+ OptionPtr opt1(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 1));
+ OptionPtr opt2(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+ OptionPtr opt3(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 3));
+
+ // the same content as opt2, but different type
+ OptionPtr opt4(new Option(Option::V6, 1, buf_.begin(), buf_.begin() + 2));
+
+ // another instance with the same type and content as opt2
+ OptionPtr opt5(new Option(Option::V6, 258, buf_.begin(), buf_.begin() + 2));
+
+ EXPECT_TRUE(opt1->equal(opt1));
+
+ EXPECT_FALSE(opt1->equal(opt2));
+ EXPECT_FALSE(opt1->equal(opt3));
+ EXPECT_FALSE(opt1->equal(opt4));
+
+ EXPECT_TRUE(opt2->equal(opt5));
+}
}
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index 37af9c6..49588e1 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -69,8 +69,9 @@ TEST(Pkt4Test, constructor) {
pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
);
- // DHCPv4 packet must be at least 236 bytes long
- EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // DHCPv4 packet must be at least 236 bytes long, with Message Type
+ // Option taking extra 3 bytes it is 239
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
EXPECT_NO_THROW(
@@ -219,8 +220,10 @@ TEST(Pkt4Test, fixedFields) {
EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText());
EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText());
- // chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), 16));
+ // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+ // long and its length depends on hlen value (it is up to 16 bytes now).
+ ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
@@ -237,7 +240,9 @@ TEST(Pkt4Test, fixedFieldsPack) {
pkt->pack();
);
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // Minimum packet size is 236 bytes + 3 bytes of mandatory
+ // DHCP Message Type Option
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
// redundant but MUCH easier for debug in gdb
const uint8_t* exp = &expectedFormat[0];
@@ -282,7 +287,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText());
// chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), Pkt4::MAX_CHADDR_LEN));
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -321,7 +326,7 @@ TEST(Pkt4Test, hwAddr) {
pkt->setHWAddr(255-macLen*10, // just weird htype
macLen,
mac);
- EXPECT_EQ(0, memcmp(expectedChaddr, pkt->getChaddr(),
+ EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
Pkt4::MAX_CHADDR_LEN));
EXPECT_NO_THROW(
@@ -416,6 +421,11 @@ TEST(Pkt4Test, sname) {
delete pkt;
}
+
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setSname(NULL, Pkt4::MAX_SNAME_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setSname(NULL, 0), InvalidParameter);
}
TEST(Pkt4Test, file) {
@@ -451,15 +461,23 @@ TEST(Pkt4Test, file) {
delete pkt;
}
+ // Check that a null argument generates an exception.
+ Pkt4 pkt4(DHCPOFFER, 1234);
+ EXPECT_THROW(pkt4.setFile(NULL, Pkt4::MAX_FILE_LEN), InvalidParameter);
+ EXPECT_THROW(pkt4.setFile(NULL, 0), InvalidParameter);
}
+/// V4 Options being used for pack/unpack testing.
+/// For test simplicity, all selected options have
+/// variable length data so as there are no restrictions
+/// on a length of their data.
static uint8_t v4Opts[] = {
- 12, 3, 0, 1, 2,
- 13, 3, 10, 11, 12,
- 14, 3, 20, 21, 22,
- 53, 1, 1, // DHCP_MESSAGE_TYPE (required to not throw exception during unpack)
- 128, 3, 30, 31, 32,
- 254, 3, 40, 41, 42,
+ 12, 3, 0, 1, 2, // Hostname
+ 14, 3, 10, 11, 12, // Merit Dump File
+ 53, 1, 2, // Message Type (required to not throw exception during unpack)
+ 60, 3, 20, 21, 22, // Class Id
+ 128, 3, 30, 31, 32, // Vendor specific
+ 254, 3, 40, 41, 42, // Reserved
};
TEST(Pkt4Test, options) {
@@ -473,22 +491,19 @@ TEST(Pkt4Test, options) {
}
boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
- boost::shared_ptr<Option> opt2(new Option(Option::V4, 13, payload[1]));
- boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[2]));
- boost::shared_ptr<Option> optMsgType(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE));
+ boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
+ boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
- optMsgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
pkt->addOption(opt1);
pkt->addOption(opt2);
pkt->addOption(opt3);
pkt->addOption(opt4);
pkt->addOption(opt5);
- pkt->addOption(optMsgType);
EXPECT_TRUE(pkt->getOption(12));
- EXPECT_TRUE(pkt->getOption(13));
+ EXPECT_TRUE(pkt->getOption(60));
EXPECT_TRUE(pkt->getOption(14));
EXPECT_TRUE(pkt->getOption(128));
EXPECT_TRUE(pkt->getOption(254));
@@ -517,6 +532,12 @@ TEST(Pkt4Test, options) {
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
+ // delOption() checks
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+ EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
EXPECT_NO_THROW(
delete pkt;
);
@@ -545,7 +566,7 @@ TEST(Pkt4Test, unpackOptions) {
);
EXPECT_TRUE(pkt->getOption(12));
- EXPECT_TRUE(pkt->getOption(13));
+ EXPECT_TRUE(pkt->getOption(60));
EXPECT_TRUE(pkt->getOption(14));
EXPECT_TRUE(pkt->getOption(128));
EXPECT_TRUE(pkt->getOption(254));
@@ -557,19 +578,19 @@ TEST(Pkt4Test, unpackOptions) {
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+2, 3)); // data len=3
- x = pkt->getOption(13);
+ x = pkt->getOption(14);
ASSERT_TRUE(x); // option 13 should exist
- EXPECT_EQ(13, x->getType()); // this should be option 13
+ EXPECT_EQ(14, x->getType()); // this should be option 13
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+7, 3)); // data len=3
- x = pkt->getOption(14);
- ASSERT_TRUE(x); // option 14 should exist
- EXPECT_EQ(14, x->getType()); // this should be option 14
+ x = pkt->getOption(60);
+ ASSERT_TRUE(x); // option 60 should exist
+ EXPECT_EQ(60, x->getType()); // this should be option 60
ASSERT_EQ(3, x->getData().size()); // it should be of length 3
EXPECT_EQ(5, x->len()); // total option length 5
- EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+12, 3)); // data len=3
+ EXPECT_EQ(0, memcmp(&x->getData()[0], v4Opts+15, 3)); // data len=3
x = pkt->getOption(128);
ASSERT_TRUE(x); // option 3 should exist
@@ -630,6 +651,23 @@ TEST(Pkt4Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
+TEST(Pkt4Test, hwaddr) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+ HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+
+ // setting NULL hardware address is not allowed
+ EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+ pkt->setHWAddr(hwaddr);
+
+ EXPECT_EQ(hw_type, pkt->getHtype());
+
+ EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+ EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/pkt6_unittest.cc b/src/lib/dhcp/tests/pkt6_unittest.cc
index b2dd16a..cdaad3b 100644
--- a/src/lib/dhcp/tests/pkt6_unittest.cc
+++ b/src/lib/dhcp/tests/pkt6_unittest.cc
@@ -170,6 +170,8 @@ TEST_F(Pkt6Test, packUnpack) {
delete clone;
}
+// This test verifies that options can be added (addOption()), retrieved
+// (getOption(), getOptions()) and deleted (delOption()).
TEST_F(Pkt6Test, addGetDelOptions) {
Pkt6* parent = new Pkt6(DHCPV6_SOLICIT, random() );
@@ -190,6 +192,25 @@ TEST_F(Pkt6Test, addGetDelOptions) {
// now there are 2 options of type 2
parent->addOption(opt3);
+ Option::OptionCollection options = parent->getOptions(2);
+ EXPECT_EQ(2, options.size()); // there should be 2 instances
+
+ // both options must be of type 2 and there must not be
+ // any other type returned
+ for (Option::OptionCollection::const_iterator x= options.begin();
+ x != options.end(); ++x) {
+ EXPECT_EQ(2, x->second->getType());
+ }
+
+ // Try to get a single option. Normally for singular options
+ // it is better to use getOption(), but getOptions() must work
+ // as well
+ options = parent->getOptions(1);
+ ASSERT_EQ(1, options.size());
+
+ EXPECT_EQ(1, (*options.begin()).second->getType());
+ EXPECT_EQ(opt1, options.begin()->second);
+
// let's delete one of them
EXPECT_EQ(true, parent->delOption(2));
@@ -205,6 +226,10 @@ TEST_F(Pkt6Test, addGetDelOptions) {
// let's try to delete - should fail
EXPECT_TRUE(false == parent->delOption(2));
+ // Finally try to get a non-existent option
+ options = parent->getOptions(1234);
+ EXPECT_EQ(0, options.size());
+
delete parent;
}
@@ -234,4 +259,52 @@ TEST_F(Pkt6Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
+// This test verifies that getName() method returns proper
+// packet type names.
+TEST_F(Pkt6Test, getName) {
+ // Check all possible packet types
+ for (int itype = 0; itype < 256; ++itype) {
+ uint8_t type = itype;
+
+ switch (type) {
+ case DHCPV6_CONFIRM:
+ EXPECT_STREQ("CONFIRM", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_DECLINE:
+ EXPECT_STREQ("DECLINE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_INFORMATION_REQUEST:
+ EXPECT_STREQ("INFORMATION_REQUEST",
+ Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REBIND:
+ EXPECT_STREQ("REBIND", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RELEASE:
+ EXPECT_STREQ("RELEASE", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_RENEW:
+ EXPECT_STREQ("RENEW", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_REQUEST:
+ EXPECT_STREQ("REQUEST", Pkt6::getName(type));
+ break;
+
+ case DHCPV6_SOLICIT:
+ EXPECT_STREQ("SOLICIT", Pkt6::getName(type));
+ break;
+
+ default:
+ EXPECT_STREQ("UNKNOWN", Pkt6::getName(type));
+ }
+ }
+}
+
+
}
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index e205aee..cc5daec 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -8,35 +8,55 @@ endif
AM_CXXFLAGS = $(B10_CXXFLAGS)
+# Define rule to build logging source files from message file
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+# Tell Automake that the dhcpsrv_messages.{cc,h} source files are created in the
+# build process, so it must create these before doing anything else. Although
+# they are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created. As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
# Some versions of GCC warn about some versions of Boost regarding
# missing initializer for members in its posix_time.
# https://svn.boost.org/trac/boost/ticket/3477
# But older GCC compilers don't have the flag.
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-CLEANFILES = *.gcno *.gcda
+# Make sure the generated files are deleted in a "clean" operation
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
+libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
+libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
+libb10_dhcpsrv_la_SOURCES += utils.h
+
+nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
libb10_dhcpsrv_la_LIBADD = $(top_builddir)/src/lib/asiolink/libb10-asiolink.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/dhcp/libb10-dhcp++.la
libb10_dhcpsrv_la_LIBADD += $(top_builddir)/src/lib/util/libb10-util.la
-libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 2:0:0
+libb10_dhcpsrv_la_LDFLAGS = -no-undefined -version-info 3:0:0
if HAVE_MYSQL
libb10_dhcpsrv_la_LDFLAGS += $(MYSQL_LIBS)
endif
@@ -47,6 +67,9 @@ if USE_CLANGPP
libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
endif
+# The message file should be in the distribution
+EXTRA_DIST = dhcpsrv_messages.mes
+
# Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox
+EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
diff --git a/src/lib/dhcpsrv/addr_utilities.cc b/src/lib/dhcpsrv/addr_utilities.cc
index a519016..d32c209 100644
--- a/src/lib/dhcpsrv/addr_utilities.cc
+++ b/src/lib/dhcpsrv/addr_utilities.cc
@@ -53,8 +53,11 @@ isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& pref
}
// First we copy the whole address as 16 bytes.
+ // We don't check that it is a valid IPv6 address and thus has
+ // the required length because it is already checked by
+ // the calling function.
uint8_t packed[V6ADDRESS_LEN];
- memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
+ memcpy(packed, &prefix.toBytes()[0], V6ADDRESS_LEN);
// If the length is divisible by 8, it is simple. We just zero out the host
// part. Otherwise we need to handle the byte that has to be partially
@@ -79,7 +82,7 @@ isc::asiolink::IOAddress firstAddrInPrefix6(const isc::asiolink::IOAddress& pref
}
// Finally, let's wrap this into nice and easy IOAddress object.
- return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
+ return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
}
/// @brief calculates the first IPv4 address in a IPv4 prefix
@@ -95,6 +98,9 @@ isc::asiolink::IOAddress firstAddrInPrefix4(const isc::asiolink::IOAddress& pref
isc_throw(isc::BadValue, "Too large netmask. 0..32 is allowed in IPv4");
}
+ // We don't check that it is a valid IPv4 address and thus has
+ // a required length of 4 bytes because it has been already
+ // checked by the calling function.
uint32_t addr = prefix;
return (IOAddress(addr & (~bitMask4[len])));
}
@@ -132,7 +138,7 @@ isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefi
// First we copy the whole address as 16 bytes.
uint8_t packed[V6ADDRESS_LEN];
- memcpy(packed, prefix.getAddress().to_v6().to_bytes().data(), 16);
+ memcpy(packed, &prefix.toBytes()[0], 16);
// if the length is divisible by 8, it is simple. We just fill the host part
// with ones. Otherwise we need to handle the byte that has to be partially
@@ -159,7 +165,7 @@ isc::asiolink::IOAddress lastAddrInPrefix6(const isc::asiolink::IOAddress& prefi
}
// Finally, let's wrap this into nice and easy IOAddress object.
- return (isc::asiolink::IOAddress::from_bytes(AF_INET6, packed));
+ return (isc::asiolink::IOAddress::fromBytes(AF_INET6, packed));
}
}; // end of anonymous namespace
@@ -168,22 +174,36 @@ namespace isc {
namespace dhcp {
isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefix,
- uint8_t len) {
- if (prefix.getFamily() == AF_INET) {
- return firstAddrInPrefix4(prefix, len);
+ uint8_t len) {
+ if (prefix.isV4()) {
+ return (firstAddrInPrefix4(prefix, len));
+
} else {
- return firstAddrInPrefix6(prefix, len);
+ return (firstAddrInPrefix6(prefix, len));
+
}
}
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len) {
- if (prefix.getFamily() == AF_INET) {
- return lastAddrInPrefix4(prefix, len);
+ if (prefix.isV4()) {
+ return (lastAddrInPrefix4(prefix, len));
+
} else {
- return lastAddrInPrefix6(prefix, len);
+ return (lastAddrInPrefix6(prefix, len));
+
}
}
+isc::asiolink::IOAddress getNetmask4(uint8_t len) {
+ if (len > 32) {
+ isc_throw(BadValue, "Invalid netmask size " << len << ", allowed range "
+ "is 0..32");
+ }
+ uint32_t x = ~bitMask4[len];
+
+ return (IOAddress(x));
+}
+
};
};
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index a1d856c..6aef574 100644
--- a/src/lib/dhcpsrv/addr_utilities.h
+++ b/src/lib/dhcpsrv/addr_utilities.h
@@ -26,8 +26,8 @@ namespace dhcp {
/// @brief returns a first address in a given prefix
///
-/// Example: For 2001:db8:1::deaf:beef and length /120 the function will return
-/// 2001:db8:1::dead:be00. See also @ref lastAddrInPrefix.
+/// Example: For 2001:db8:1\::deaf:beef and length /120 the function will return
+/// 2001:db8:1\::dead:be00. See also @ref lastAddrInPrefix.
///
/// @todo It currently works for v6 only and will throw if v4 address is passed.
///
@@ -40,8 +40,8 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
/// @brief returns a last address in a given prefix
///
-/// Example: For 2001:db8:1::deaf:beef and length /112 the function will return
-/// 2001:db8:1::dead:ffff. See also @ref firstAddrInPrefix.
+/// Example: For 2001:db8:1\::deaf:beef and length /112 the function will return
+/// 2001:db8:1\::dead:ffff. See also @ref firstAddrInPrefix.
///
/// @todo It currently works for v6 only and will throw if v4 address is passed.
///
@@ -52,6 +52,11 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len);
+/// @brief generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+isc::asiolink::IOAddress getNetmask4(uint8_t len);
+
};
};
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 4f20276..7a64dac 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,7 +16,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <cstring>
-
+#include <vector>
#include <string.h>
using namespace isc::asiolink;
@@ -30,33 +30,35 @@ AllocEngine::IterativeAllocator::IterativeAllocator()
isc::asiolink::IOAddress
AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress& addr) {
+ // Get a buffer holding an address.
+ const std::vector<uint8_t>& vec = addr.toBytes();
+ // Get the address length.
+ const int len = vec.size();
+
+ // Since the same array will be used to hold the IPv4 and IPv6
+ // address we have to make sure that the size of the array
+ // we allocate will work for both types of address.
+ BOOST_STATIC_ASSERT(V4ADDRESS_LEN <= V6ADDRESS_LEN);
uint8_t packed[V6ADDRESS_LEN];
- int len;
- // First we copy the whole address as 16 bytes.
- if (addr.getFamily()==AF_INET) {
- // IPv4
- std::memcpy(packed, addr.getAddress().to_v4().to_bytes().data(), 4);
- len = 4;
- } else {
- // IPv6
- std::memcpy(packed, addr.getAddress().to_v6().to_bytes().data(), 16);
- len = 16;
- }
+ // Copy the address. It can be either V4 or V6.
+ std::memcpy(packed, &vec[0], len);
+ // Start increasing the least significant byte
for (int i = len - 1; i >= 0; --i) {
++packed[i];
+ // if we haven't overflowed (0xff -> 0x0), than we are done
if (packed[i] != 0) {
break;
}
}
- return (IOAddress::from_bytes(addr.getFamily(), packed));
+ return (IOAddress::fromBytes(addr.getFamily(), packed));
}
isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
+AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
const DuidPtr&,
const IOAddress&) {
@@ -65,14 +67,14 @@ AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
// perhaps restaring the server).
IOAddress last = subnet->getLastAllocated();
- const Pool6Collection& pools = subnet->getPools();
+ const PoolCollection& pools = subnet->getPools();
- if (pools.size() == 0) {
+ if (pools.empty()) {
isc_throw(AllocFailed, "No pools defined in selected subnet");
}
// first we need to find a pool the last address belongs to.
- Pool6Collection::const_iterator it;
+ PoolCollection::const_iterator it;
for (it = pools.begin(); it != pools.end(); ++it) {
if ((*it)->inRange(last)) {
break;
@@ -122,9 +124,9 @@ AllocEngine::HashedAllocator::HashedAllocator()
isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
@@ -135,9 +137,9 @@ AllocEngine::RandomAllocator::RandomAllocator()
isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
@@ -189,7 +191,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
// the hint is valid and not currently used, let's create a lease for it
- Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
// It can happen that the lease allocation failed (we could have lost
// the race condition. That means that the hint is lo longer usable and
@@ -197,9 +199,31 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
if (lease) {
return (lease);
}
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fake_allocation));
+ }
+
}
}
+ // Hint is in the pool but is not available. Search the pool until first of
+ // the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust number of tries
+ //
+ // @todo: Current code does not handle pool exhaustion well. It will be
+ // improved. Current problems:
+ // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+ // 10 addresses), we will iterate over it 100 times before giving up
+ // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+ // 3. the whole concept of infinite attempts is just asking for infinite loop
+ // We may consider some form or reference counting (this pool has X addresses
+ // left), but this has one major problem. We exactly control allocation
+ // moment, but we currently do not control expiration time at all
+
unsigned int i = attempts_;
do {
IOAddress candidate = allocator_->pickAddress(subnet, duid, hint);
@@ -208,10 +232,10 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
Lease6Ptr existing = LeaseMgrFactory::instance().getLease6(candidate);
- // there's no existing lease for selected candidate, so it is
- // free. Let's allocate it.
if (!existing) {
- Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
fake_allocation);
if (lease) {
return (lease);
@@ -220,6 +244,11 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
// Although the address was free just microseconds ago, it may have
// been taken just now. If the lease insertion fails, we continue
// allocation attempts.
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, duid, iaid,
+ fake_allocation));
+ }
}
// continue trying allocation until we run out of attempts
@@ -231,11 +260,230 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
<< " tries");
}
-Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
- const DuidPtr& duid,
- uint32_t iaid,
- const IOAddress& addr,
- bool fake_allocation /*= false */ ) {
+Lease4Ptr
+AllocEngine::allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& hint,
+ bool fake_allocation /* = false */ ) {
+
+ // Allocator is always created in AllocEngine constructor and there is
+ // currently no other way to set it, so that check is not really necessary.
+ if (!allocator_) {
+ isc_throw(InvalidOperation, "No allocator selected");
+ }
+
+ // Check if there's existing lease for that subnet/clientid/hwaddr combination.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID());
+ if (existing) {
+ // We have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ if (existing) {
+ return (existing);
+ }
+
+ // If renewal failed (e.g. the lease no longer matches current configuration)
+ // let's continue the allocation process
+ }
+
+ if (clientid) {
+ existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
+ if (existing) {
+ // we have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ // @todo: produce a warning. We haven't found him using MAC address, but
+ // we found him using client-id
+ if (existing) {
+ return (existing);
+ }
+ }
+ }
+
+ // check if the hint is in pool and is available
+ if (subnet->inPool(hint)) {
+ existing = LeaseMgrFactory::instance().getLease4(hint);
+ if (!existing) {
+ /// @todo: Check if the hint is reserved once we have host support
+ /// implemented
+
+ // The hint is valid and not currently used, let's create a lease for it
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+
+ // It can happen that the lease allocation failed (we could have lost
+ // the race condition. That means that the hint is lo longer usable and
+ // we need to continue the regular allocation path.
+ if (lease) {
+ return (lease);
+ }
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+
+ }
+ }
+
+ // Hint is in the pool but is not available. Search the pool until first of
+ // the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust the number of tries
+ //
+ // @todo: Current code does not handle pool exhaustion well. It will be
+ // improved. Current problems:
+ // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+ // 10 addresses), we will iterate over it 100 times before giving up
+ // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+ // 3. the whole concept of infinite attempts is just asking for infinite loop
+ // We may consider some form or reference counting (this pool has X addresses
+ // left), but this has one major problem. We exactly control allocation
+ // moment, but we currently do not control expiration time at all
+
+ unsigned int i = attempts_;
+ do {
+ IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
+
+ /// @todo: check if the address is reserved once we have host support
+ /// implemented
+
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
+ if (!existing) {
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
+ fake_allocation);
+ if (lease) {
+ return (lease);
+ }
+
+ // Although the address was free just microseconds ago, it may have
+ // been taken just now. If the lease insertion fails, we continue
+ // allocation attempts.
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+ }
+
+ // Continue trying allocation until we run out of attempts
+ // (or attempts are set to 0, which means infinite)
+ --i;
+ } while ( i || !attempts_);
+
+ isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
+ << " tries");
+}
+
+Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */) {
+
+ lease->subnet_id_ = subnet->getID();
+ lease->hwaddr_ = hwaddr->hwaddr_;
+ lease->client_id_ = clientid;
+ lease->cltt_ = time(NULL);
+ lease->t1_ = subnet->getT1();
+ lease->t2_ = subnet->getT2();
+ lease->valid_lft_ = subnet->getValid();
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(lease);
+ }
+
+ return (lease);
+}
+
+Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
+ const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ uint32_t iaid,
+ bool fake_allocation /*= false */ ) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->iaid_ = iaid;
+ expired->duid_ = duid;
+ expired->preferred_lft_ = subnet->getPreferred();
+ expired->valid_lft_ = subnet->getValid();
+ expired->t1_ = subnet->getT1();
+ expired->t2_ = subnet->getT2();
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = subnet->getID();
+ expired->fixed_ = false;
+ expired->hostname_ = std::string("");
+ expired->fqdn_fwd_ = false;
+ expired->fqdn_rev_ = false;
+
+ /// @todo: log here that the lease was reused (there's ticket #2524 for
+ /// logging in libdhcpsrv)
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease6(expired);
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+ const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation /*= false */ ) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->client_id_ = clientid;
+ expired->hwaddr_ = hwaddr->hwaddr_;
+ expired->valid_lft_ = subnet->getValid();
+ expired->t1_ = subnet->getT1();
+ expired->t2_ = subnet->getT2();
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = subnet->getID();
+ expired->fixed_ = false;
+ expired->hostname_ = std::string("");
+ expired->fqdn_fwd_ = false;
+ expired->fqdn_rev_ = false;
+
+ /// @todo: log here that the lease was reused (there's ticket #2524 for
+ /// logging in libdhcpsrv)
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(expired);
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ uint32_t iaid,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
@@ -269,6 +517,53 @@ Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
}
}
+Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
+ const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
+ if (!hwaddr) {
+ isc_throw(BadValue, "Can't create a lease with NULL HW address");
+ }
+ time_t now = time(NULL);
+
+ // @todo: remove this kludge after ticket #2590 is implemented
+ std::vector<uint8_t> local_copy;
+ if (clientid) {
+ local_copy = clientid->getDuid();
+ }
+
+ Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+ &local_copy[0], local_copy.size(), subnet->getValid(),
+ subnet->getT1(), subnet->getT2(), now,
+ subnet->getID()));
+
+ if (!fake_allocation) {
+ // That is a real (REQUEST) allocation
+ bool status = LeaseMgrFactory::instance().addLease(lease);
+ if (status) {
+ return (lease);
+ } else {
+ // One of many failures with LeaseMgr (e.g. lost connection to the
+ // database, database failed etc.). One notable case for that
+ // is that we are working in multi-process mode and we lost a race
+ // (some other process got that address first)
+ return (Lease4Ptr());
+ }
+ } else {
+ // That is only fake (DISCOVER) allocation
+
+ // It is for OFFER only. We should not insert the lease into LeaseMgr,
+ // but rather check that we could have inserted it.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
+ if (!existing) {
+ return (lease);
+ } else {
+ return (Lease4Ptr());
+ }
+ }
+}
+
AllocEngine::~AllocEngine() {
// no need to delete allocator. smart_ptr will do the trick for us
}
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index 940b1f4..c6cbc35 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease_mgr.h>
@@ -65,8 +66,14 @@ protected:
/// reserved - AllocEngine will check that and will call pickAddress
/// again if necessary. The number of times this method is called will
/// increase as the number of available leases will decrease.
+ ///
+ /// @param subnet next address will be returned from pool of that subnet
+ /// @param duid Client's DUID
+ /// @param hint client's hint
+ ///
+ /// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint) = 0;
/// @brief virtual destructor
@@ -96,7 +103,7 @@ protected:
/// @param hint client's hint (ignored)
/// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet,
+ pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
private:
@@ -125,7 +132,7 @@ protected:
/// @param duid Client's DUID
/// @param hint a hint (last address that was picked)
/// @return selected address
- virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
+ virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -148,7 +155,7 @@ protected:
/// @param hint the last address that was picked (ignored)
/// @return a random address from the pool
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -174,6 +181,48 @@ protected:
/// we give up (0 means unlimited)
AllocEngine(AllocType engine_type, unsigned int attempts);
+ /// @brief Allocates an IPv4 lease
+ ///
+ /// This method uses currently selected allocator to pick an address from
+ /// specified subnet, creates a lease for that address and then inserts
+ /// it into LeaseMgr (if this allocation is not fake).
+ ///
+ /// @param subnet subnet the allocation should come from
+ /// @param clientid Client identifier
+ /// @param hwaddr client's hardware address info
+ /// @param hint a hint that the client provided
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return Allocated IPv4 lease (or NULL if allocation failed)
+ Lease4Ptr
+ allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& hint,
+ bool fake_allocation);
+
+ /// @brief Renews a IPv4 lease
+ ///
+ /// Since both request and renew are implemented in DHCPv4 as the sending of
+ /// a REQUEST packet, it is difficult to easily distinguish between those
+ /// cases. Therefore renew for DHCPv4 is done in the allocation engine.
+ /// This method is also used when client crashed/rebooted and tries
+ /// to get a new lease. It thinks that it gets a new lease, but in fact
+ /// we are only renewing the still valid lease for that client.
+ ///
+ /// @param subnet subnet the client is attached to
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param lease lease to be renewed
+ /// @param renewed lease (typically the same passed as lease parameter)
+ /// or NULL if the lease cannot be renewed
+ Lease4Ptr
+ renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */);
+
/// @brief Allocates an IPv6 lease
///
/// This method uses currently selected allocator to pick an address from
@@ -181,7 +230,7 @@ protected:
/// it into LeaseMgr (if this allocation is not fake).
///
/// @param subnet subnet the allocation should come from
- /// @param duid Client'd DUID
+ /// @param duid Client's DUID
/// @param iaid iaid field from the IA_NA container that client sent
/// @param hint a hint that the client provided
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
@@ -198,6 +247,25 @@ protected:
virtual ~AllocEngine();
private:
+ /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+ ///
+ /// Creates a lease based on specified parameters and tries to insert it
+ /// into the database. That may fail in some cases, e.g. when there is another
+ /// allocation process and we lost a race to a specific lease.
+ ///
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param addr an address that was selected and is confirmed to be available
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return allocated lease (or NULL in the unlikely case of the lease just
+ /// becomed unavailable)
+ Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
/// @brief creates a lease and inserts it in LeaseMgr if necessary
///
/// Creates a lease based on specified parameters and tries to insert it
@@ -212,9 +280,46 @@ private:
/// an address for SOLICIT that is not really allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
/// becomed unavailable)
- Lease6Ptr createLease(const Subnet6Ptr& subnet, const DuidPtr& duid,
- uint32_t iaid, const isc::asiolink::IOAddress& addr,
- bool fake_allocation = false);
+ Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ uint32_t iaid, const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
+ /// @brief Reuses expired IPv4 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return refreshed lease
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation = false);
+
+ /// @brief Reuses expired IPv6 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. SOLICIT, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param subnet subnet the lease is allocated from
+ /// @param duid client's DUID
+ /// @param iaid IAID from the IA_NA container the client sent to us
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for SOLICIT that is not really allocated (true)
+ /// @return refreshed lease
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease6Ptr reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet,
+ const DuidPtr& duid, uint32_t iaid,
+ bool fake_allocation = false);
/// @brief a pointer to currently used allocator
boost::shared_ptr<Allocator> allocator_;
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 3c46b13..c6a190f 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -13,7 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
using namespace isc::asiolink;
using namespace isc::util;
@@ -21,15 +23,125 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
-
-
-
CfgMgr&
CfgMgr::instance() {
static CfgMgr cfg_mgr;
return (cfg_mgr);
}
+void
+CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces4_.find(space->getName());
+ if (it != spaces4_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces4_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces6_.find(space->getName());
+ if (it != spaces6_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces6_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space) {
+ // @todo we need better validation of the provided option space name here.
+ // This will be implemented when #2313 is merged.
+ if (option_space.empty()) {
+ isc_throw(BadValue, "option space name must not be empty");
+ } else if (!def) {
+ // Option definition must point to a valid object.
+ isc_throw(MalformedOptionDefinition, "option definition must not be NULL");
+
+ } else if (getOptionDef(option_space, def->getCode())) {
+ // Option definition must not be overriden.
+ isc_throw(DuplicateOptionDefinition, "option definition already added"
+ << " to option space " << option_space);
+
+ } else if ((option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+ (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
+ // We must not override standard (assigned) option. The standard options
+ // belong to dhcp4 or dhcp6 option space.
+ isc_throw(BadValue, "unable to override definition of option '"
+ << def->getCode() << "' in standard option space '"
+ << option_space << "'.");
+
+ }
+ // Actually add a new item.
+ option_def_spaces_.addItem(def, option_space);
+}
+
+OptionDefContainerPtr
+CfgMgr::getOptionDefs(const std::string& option_space) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ return (option_def_spaces_.getItems(option_space));
+}
+
+OptionDefinitionPtr
+CfgMgr::getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ // Get a reference to option definitions for a particular option space.
+ OptionDefContainerPtr defs = getOptionDefs(option_space);
+ // If there are no matching option definitions then return the empty pointer.
+ if (!defs || defs->empty()) {
+ return (OptionDefinitionPtr());
+ }
+ // If there are some option definitions for a particular option space
+ // use an option code to get the one we want.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ // If there is no definition that matches option code, return empty pointer.
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDefinitionPtr());
+ }
+ // If there is more than one definition matching an option code, return
+ // the first one. This should not happen because we check for duplicates
+ // when addOptionDef is called.
+ return (*range.first);
+}
+
+Subnet6Ptr
+CfgMgr::getSubnet6(const std::string& iface) {
+
+ if (!iface.length()) {
+ return (Subnet6Ptr());
+ }
+
+ // If there is more than one, we need to choose the proper one
+ for (Subnet6Collection::iterator subnet = subnets6_.begin();
+ subnet != subnets6_.end(); ++subnet) {
+ if (iface == (*subnet)->getIface()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE)
+ .arg((*subnet)->toText()).arg(iface);
+ return (*subnet);
+ }
+ }
+ return (Subnet6Ptr());
+}
+
Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
@@ -42,18 +154,27 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET6)
+ .arg(subnets6_[0]->toText()).arg(hint.toText());
return (subnets6_[0]);
}
// If there is more than one, we need to choose the proper one
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
+
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
+ .arg(hint.toText());
return (Subnet6Ptr());
}
@@ -65,6 +186,8 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+ .arg(subnet->toText());
subnets6_.push_back(subnet);
}
@@ -80,6 +203,9 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets4_.size() == 1) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET4)
+ .arg(subnets4_[0]->toText()).arg(hint.toText());
return (subnets4_[0]);
}
@@ -87,20 +213,41 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
for (Subnet4Collection::iterator subnet = subnets4_.begin();
subnet != subnets4_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET4)
+ .arg(hint.toText());
return (Subnet4Ptr());
}
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+ .arg(subnet->toText());
subnets4_.push_back(subnet);
}
+void CfgMgr::deleteOptionDefs() {
+ option_def_spaces_.clearItems();
+}
+
+void CfgMgr::deleteSubnets4() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
+ subnets4_.clear();
+}
+
+void CfgMgr::deleteSubnets6() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
+ subnets6_.clear();
+}
+
CfgMgr::CfgMgr() {
}
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 5bff64a..bc4ffde 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,9 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/option_space.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <util/buffer.h>
@@ -43,6 +46,7 @@ namespace dhcp {
/// Below is a sketch of configuration inheritance (not implemented yet).
/// Let's investigate the following configuration:
///
+/// @code
/// preferred-lifetime 500;
/// valid-lifetime 1000;
/// subnet6 2001:db8:1::/48 {
@@ -52,6 +56,8 @@ namespace dhcp {
/// valid-lifetime 2000;
/// pool6 2001::db8:2::1 - 2001::db8:2::ff;
/// };
+/// @endcode
+///
/// Parameters defined in a global scope are applicable to everything until
/// they are overwritten in a smaller scope, in this case subnet6.
/// In the example above, the first subnet6 has preferred lifetime of 500s
@@ -74,6 +80,71 @@ public:
/// accessing it.
static CfgMgr& instance();
+ /// @brief Add new option definition.
+ ///
+ /// @param def option definition to be added.
+ /// @param option_space name of the option space to add definition to.
+ ///
+ /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+ /// option definition already exists.
+ /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+ /// an option definition is NULL.
+ /// @throw isc::BadValue when the option space name is empty or
+ /// when trying to override the standard option (in dhcp4 or dhcp6
+ /// option space).
+ void addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space);
+
+ /// @brief Return option definitions for particular option space.
+ ///
+ /// @param option_space option space.
+ ///
+ /// @return pointer to the collection of option definitions for
+ /// the particular option space. The option collection is empty
+ /// if no option exists for the option space specified.
+ OptionDefContainerPtr
+ getOptionDefs(const std::string& option_space) const;
+
+ /// @brief Return option definition for a particular option space and code.
+ ///
+ /// @param option_space option space.
+ /// @param option_code option code.
+ ///
+ /// @return an option definition or NULL pointer if option definition
+ /// has not been found.
+ OptionDefinitionPtr getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const;
+
+ /// @brief Adds new DHCPv4 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace4(const OptionSpacePtr& space);
+
+ /// @brief Adds new DHCPv6 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace6(const OptionSpacePtr& space);
+
+ /// @brief Return option spaces for DHCPv4.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces4() const {
+ return (spaces4_);
+ }
+
+ /// @brief Return option spaces for DHCPv6.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces6() const {
+ return (spaces6_);
+ }
+
/// @brief get IPv6 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
@@ -83,27 +154,45 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object (or NULL if no suitable match was fount)
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+ /// @brief get IPv6 subnet by interface name
+ ///
+ /// Finds a matching local subnet, based on interface name. This
+ /// is used for selecting subnets that were explicitly marked by the
+ /// user as reachable over specified network interface.
+ /// @param iface_name interface name
+ /// @return a subnet object (or NULL if no suitable match was fount)
+ Subnet6Ptr getSubnet6(const std::string& iface_name);
+
/// @brief get IPv6 subnet by interface-id
///
/// Another possibility to find a subnet is based on interface-id.
///
/// @param interface_id content of interface-id option returned by a relay
+ ///
+ /// @return a subnet object
/// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet
+ ///
+ /// @param subnet new subnet to be added.
void addSubnet6(const Subnet6Ptr& subnet);
+ /// @brief Delete all option definitions.
+ void deleteOptionDefs();
+
/// @todo: Add subnet6 removal routines. Currently it is not possible
/// to remove subnets. The only case where subnet6 removal would be
/// needed is a dynamic server reconfiguration - a use case that is not
/// planned to be supported any time soon.
- /// @brief removes all subnets
+ /// @brief removes all IPv6 subnets
///
- /// This method removes all existing subnets. It is used during
+ /// This method removes all existing IPv6 subnets. It is used during
/// reconfiguration - old configuration is wiped and new definitions
/// are used to recreate subnets.
///
@@ -111,9 +200,7 @@ public:
/// between old and new configuration is tricky. For example: is
/// 2000::/64 and 2000::/48 the same subnet or is it something
/// completely new?
- void deleteSubnets6() {
- subnets6_.clear();
- }
+ void deleteSubnets6();
/// @brief get IPv4 subnet by address
///
@@ -124,13 +211,25 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object
Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
/// @brief adds a subnet4
void addSubnet4(const Subnet4Ptr& subnet);
/// @brief removes all IPv4 subnets
- void removeSubnets4();
+ ///
+ /// This method removes all existing IPv4 subnets. It is used during
+ /// reconfiguration - old configuration is wiped and new definitions
+ /// are used to recreate subnets.
+ ///
+ /// @todo Implement more intelligent approach. Note that comparison
+ /// between old and new configuration is tricky. For example: is
+ /// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
+ /// completely new?
+ void deleteSubnets4();
+
protected:
/// @brief Protected constructor.
@@ -159,6 +258,22 @@ protected:
/// pattern will use calling inRange() method on each subnet until
/// a match is found.
Subnet4Collection subnets4_;
+
+private:
+
+ /// @brief A collection of option definitions.
+ ///
+ /// A collection of option definitions that can be accessed
+ /// using option space name they belong to.
+ OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> option_def_spaces_;
+
+ /// @brief Container for defined DHCPv6 option spaces.
+ OptionSpaceCollection spaces6_;
+
+ /// @brief Container for defined DHCPv4 option spaces.
+ OptionSpaceCollection spaces4_;
+
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index 8eeb5c5..a3b3b0c 100644
--- a/src/lib/dhcpsrv/database_backends.dox
+++ b/src/lib/dhcpsrv/database_backends.dox
@@ -1,5 +1,5 @@
/**
- @page dhcp-database-backends DHCP Database Back-Ends
+ @page dhcpDatabaseBackends DHCP Database Back-Ends
All DHCP lease data is stored in some form of database, the interface
to this being through the Lease Manager.
@@ -8,6 +8,17 @@
the abstract isc::dhcp::LeaseMgr class. This provides methods to
create, retrieve, modify and delete leases in the database.
+ There are currently two available Lease Managers, MySQL and Memfile:
+
+ - The MySQL lease manager uses the freely available MySQL as its backend
+ database. This is not included in BIND 10 DHCP by default:
+ the --with-dhcp-mysql switch must be supplied to "configure" for support
+ to be compiled into the software.
+ - Memfile is an in-memory lease database, with (currently) nothing being
+ written to persistent storage. The long-term plans for the backend do
+ include the ability to store this on disk, but it is currently a
+ low-priority item.
+
@section dhcpdb-instantiation Instantiation of Lease Managers
A lease manager is instantiated through the LeaseMgrFactory class. This
@@ -32,6 +43,8 @@
- <b>type</b> - specifies the type of database backend. The following values
for the type keyword are supported:
+ - <B>memfile</b> - In-memory database. Nothing is written to any
+ external storage, so this should not be used in production.
- <b>mysql</b> - Use MySQL as the database
The following sections list the database-specific keywords:
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
new file mode 100644
index 0000000..d3d05f6
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -0,0 +1,156 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP_CONFIG_PARSER_H
+#define DHCP_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if an error occurs while configuring
+/// DHCP server.
+class DhcpConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Forward declaration to DhcpConfigParser class.
+///
+/// It is only needed here to define types that are
+/// based on this class before the class definition.
+class DhcpConfigParser;
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief Collection of parsers.
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+/// @brief Base abstract class for all DHCP parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+class DhcpConfigParser {
+ ///
+ /// @name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+
+ // Private construtor and assignment operator assures that nobody
+ // will be able to copy or assign a parser. There are no defined
+ // bodies for them.
+ DhcpConfigParser(const DhcpConfigParser& source);
+ DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+ /// @brief The default constructor.
+ ///
+ /// This is intentionally defined as @c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ DhcpConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~DhcpConfigParser() {}
+ //@}
+
+ /// @brief Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of @c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// @c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of @c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once in the
+ /// life of the object. Although multiple calls are not prohibited
+ /// by the interface, the behavior is undefined.
+ ///
+ /// @param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// @brief Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in @c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via @c configureDhcp4Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after @c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+
+protected:
+
+ /// @brief Return the parsed entry from the provided storage.
+ ///
+ /// This method returns the parsed entry from the provided
+ /// storage. If the entry is not found, then exception is
+ /// thrown.
+ ///
+ /// @param param_id name of the configuration entry.
+ /// @param storage storage where the entry should be searched.
+ /// @tparam ReturnType type of the returned value.
+ /// @tparam StorageType type of the storage.
+ ///
+ /// @throw DhcpConfigError if the entry has not been found
+ /// in the storage.
+ template<typename ReturnType, typename StorageType>
+ static ReturnType getParam(const std::string& param_id,
+ const StorageType& storage) {
+ typename StorageType::const_iterator param = storage.find(param_id);
+ if (param == storage.end()) {
+ isc_throw(DhcpConfigError, "missing parameter '"
+ << param_id << "'");
+ }
+ ReturnType value = param->second;
+ return (value);
+ }
+
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcpdb_create.mysql b/src/lib/dhcpsrv/dhcpdb_create.mysql
index 7a292ec..695091d 100644
--- a/src/lib/dhcpsrv/dhcpdb_create.mysql
+++ b/src/lib/dhcpsrv/dhcpdb_create.mysql
@@ -34,7 +34,7 @@ CREATE TABLE lease4 (
address INT UNSIGNED PRIMARY KEY NOT NULL, # IPv4 address
hwaddr VARBINARY(20), # Hardware address
client_id VARBINARY(128), # Client ID
- lease_time INT UNSIGNED, # Length of the lease (seconds)
+ valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
expire TIMESTAMP, # Expiration time of the lease
subnet_id INT UNSIGNED # Subnet identification
) ENGINE = INNODB;
@@ -43,7 +43,7 @@ CREATE TABLE lease4 (
# N.B. The use of a VARCHAR for the address is temporary for development:
# it will eventually be replaced by BINARY(16).
CREATE TABLE lease6 (
- address VARCHAR(40) PRIMARY KEY NOT NULL, # IPv6 address
+ address VARCHAR(39) PRIMARY KEY NOT NULL, # IPv6 address
duid VARBINARY(128), # DUID
valid_lifetime INT UNSIGNED, # Length of the lease (seconds)
expire TIMESTAMP, # Expiration time of the lease
@@ -72,12 +72,17 @@ COMMIT;
# This table is only modified during schema upgrades. For historical reasons
# (related to the names of the columns in the BIND 10 DNS database file), the
# first column is called "version" and not "major".
+#
+# NOTE: this MUST be kept in step with src/lib/dhcpsrv/tests/schema_copy.h,
+# which defines the schema for the unit tests. If you are updating
+# the version number, the schema has changed: please ensure that
+# schema_copy.h has been updated as well.
CREATE TABLE schema_version (
version INT PRIMARY KEY NOT NULL, # Major version number
minor INT # Minor version number
);
START TRANSACTION;
-INSERT INTO schema_version VALUES (0, 1);
+INSERT INTO schema_version VALUES (1, 0);
COMMIT;
# Notes:
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..6cbfb0d
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..9b6350a
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+
+/// \brief DHCP server library Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..8a7610b
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,237 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv4
+subnets in its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv6
+subnets in its database.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet for a packet received over given interface.
+This particular subnet was selected, because it was specified as being directly
+reachable over given interface. (see 'interface' parameter in subnet6 definition).
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_COMMIT commiting to memory file database
+The code has issued a commit call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database. The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the memory file database for a client with the specified
+IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_COMMIT commiting to MySQl database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the commital may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the MySQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string. The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index 5f4dc2f..6608b14 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -29,15 +29,21 @@
using namespace std;
-using namespace isc::dhcp;
+namespace isc {
+namespace dhcp {
-Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
- uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
- uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
- :type_(type), addr_(addr), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
- preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
- subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
- fqdn_rev_(false) {
+Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt)
+ :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt),
+ subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false) {
+}
+
+Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
+ DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
+ uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
+ : Lease(addr, t1, t2, valid, subnet_id, 0/*cltt*/),
+ type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+ preferred_lft_(preferred) {
if (!duid) {
isc_throw(InvalidOperation, "DUID must be specified for a lease");
}
@@ -45,6 +51,14 @@ Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr dui
cltt_ = time(NULL);
}
+bool Lease::expired() const {
+
+ // Let's use int64 to avoid problems with negative/large uint32 values
+ int64_t expire_time = cltt_ + valid_lft_;
+ return (expire_time < time(NULL));
+}
+
+
std::string LeaseMgr::getParameter(const std::string& name) const {
ParameterMap::const_iterator param = parameters_.find(name);
if (param == parameters_.end()) {
@@ -54,7 +68,7 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
}
std::string
-Lease6::toText() {
+Lease6::toText() const {
ostringstream stream;
stream << "Type: " << static_cast<int>(type_) << " (";
@@ -82,17 +96,62 @@ Lease6::toText() {
return (stream.str());
}
+std::string
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_.toText() << "\n"
+ << "Valid life: " << valid_lft_ << "\n"
+ << "T1: " << t1_ << "\n"
+ << "T2: " << t2_ << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n";
+
+ return (stream.str());
+}
+
+
+bool
+Lease4::operator==(const Lease4& other) const {
+ return (
+ addr_ == other.addr_ &&
+ ext_ == other.ext_ &&
+ hwaddr_ == other.hwaddr_ &&
+ *client_id_ == *other.client_id_ &&
+ t1_ == other.t1_ &&
+ t2_ == other.t2_ &&
+ valid_lft_ == other.valid_lft_ &&
+ cltt_ == other.cltt_ &&
+ subnet_id_ == other.subnet_id_ &&
+ fixed_ == other.fixed_ &&
+ hostname_ == other.hostname_ &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_ &&
+ comments_ == other.comments_
+ );
+}
+
bool
Lease6::operator==(const Lease6& other) const {
return (
- type_ == other.type_ &&
addr_ == other.addr_ &&
+ type_ == other.type_ &&
prefixlen_ == other.prefixlen_ &&
iaid_ == other.iaid_ &&
*duid_ == *other.duid_ &&
preferred_lft_ == other.preferred_lft_ &&
valid_lft_ == other.valid_lft_ &&
+ t1_ == other.t1_ &&
+ t2_ == other.t2_ &&
cltt_ == other.cltt_ &&
- subnet_id_ == other.subnet_id_
- );
+ subnet_id_ == other.subnet_id_ &&
+ fixed_ == other.fixed_ &&
+ hostname_ == other.hostname_ &&
+ fqdn_fwd_ == other.fqdn_fwd_ &&
+ fqdn_rev_ == other.fqdn_rev_ &&
+ comments_ == other.comments_
+ );
}
+
+} // namespace isc::dhcp
+} // namespace isc
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 0e1f8d8..f781576 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -25,12 +26,13 @@
#include <boost/shared_ptr.hpp>
#include <fstream>
+#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>
-/// @file dhcp/lease_mgr.h
+/// @file lease_mgr.h
/// @brief An abstract API for lease database
///
/// This file contains declarations of Lease4, Lease6 and LeaseMgr classes.
@@ -61,8 +63,6 @@
/// Nevertheless, we hope to have failover protocol eventually implemented in
/// the Kea.
-#include <iostream>
-
namespace isc {
namespace dhcp {
@@ -87,6 +87,13 @@ public:
isc::Exception(file, line, what) {}
};
+/// @brief Multiple lease records found where one expected
+class MultipleRecords : public Exception {
+public:
+ MultipleRecords(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
/// @brief Attempt to update lease that was not there
class NoSuchLease : public Exception {
public:
@@ -94,211 +101,236 @@ public:
isc::Exception(file, line, what) {}
};
-/// @brief Structure that holds a lease for IPv4 address
+/// @brief Data is truncated
+class DataTruncated : public Exception {
+public:
+ DataTruncated(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// @brief a common structure for IPv4 and IPv6 leases
///
-/// For performance reasons it is a simple structure, not a class. If we chose
-/// make it a class, all fields would have to made private and getters/setters
-/// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access is warranted.
-struct Lease4 {
- /// IPv4 address
- isc::asiolink::IOAddress addr_;
+/// This structure holds all information that is common between IPv4 and IPv6
+/// leases.
+struct Lease {
- /// @brief Address extension
- ///
- /// It is envisaged that in some cases IPv4 address will be accompanied with some
- /// additional data. One example of such use are Address + Port solutions (or
- /// Port-restricted Addresses), where several clients may get the same address, but
- /// different port ranges. This feature is not expected to be widely used.
- /// Under normal circumstances, the value should be 0.
- uint32_t ext_;
+ Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt);
- /// @brief hardware address
- std::vector<uint8_t> hwaddr_;
+ virtual ~Lease() {}
- /// @brief client identifier
- boost::shared_ptr<ClientId> client_id_;
+ /// @brief IPv4 ot IPv6 address
+ ///
+ /// IPv4, IPv6 address or, in the case of a prefix delegation, the prefix.
+ isc::asiolink::IOAddress addr_;
- /// @brief renewal timer
+ /// @brief Renewal timer
///
- /// Specifies renewal time. Although technically it is a property of IA container,
- /// not the address itself, since our data model does not define separate IA
- /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
- /// for the same IA, each must have consistent T1 and T2 values. Specified in
- /// seconds since cltt.
+ /// Specifies renewal time. Although technically it is a property of the
+ /// IA container and not the address itself, since our data model does not
+ /// define a separate IA entity, we are keeping it in the lease. In the
+ /// case of multiple addresses/prefixes for the same IA, each must have
+ /// consistent T1 and T2 values. This is specified in seconds since cltt.
uint32_t t1_;
- /// @brief rebinding timer
+ /// @brief Rebinding timer
///
- /// Specifies rebinding time. Although technically it is a property of IA container,
- /// not the address itself, since our data model does not define separate IA
- /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
- /// for the same IA, each must have consistent T1 and T2 values. Specified in
- /// seconds since cltt.
+ /// Specifies rebinding time. Although technically it is a property of the
+ /// IA container and not the address itself, since our data model does not
+ /// define a separate IA entity, we are keeping it in the lease. In the
+ /// case of multiple addresses/prefixes for the same IA, each must have
+ /// consistent T1 and T2 values. This is specified in seconds since cltt.
uint32_t t2_;
- /// @brief valid lifetime
+ /// @brief Valid lifetime
///
- /// Expressed as number of seconds since cltt
+ /// Expressed as number of seconds since cltt.
uint32_t valid_lft_;
- /// @brief client last transmission time
+ /// @brief Client last transmission time
///
- /// Specifies a timestamp, when last transmission from a client was received.
+ /// Specifies a timestamp giving the time when the last transmission from a
+ /// client was received.
time_t cltt_;
/// @brief Subnet identifier
///
- /// Specifies subnet-id of the subnet that the lease belongs to
+ /// Specifies the identification of the subnet to which the lease belongs.
SubnetID subnet_id_;
- /// @brief Is this a fixed lease?
+ /// @brief Fixed lease?
///
/// Fixed leases are kept after they are released/expired.
bool fixed_;
- /// @brief client hostname
+ /// @brief Client hostname
///
/// This field may be empty
std::string hostname_;
- /// @brief did we update AAAA record for this lease?
+ /// @brief Forward zone updated?
+ ///
+ /// Set true if the DNS AAAA record for this lease has been updated.
bool fqdn_fwd_;
- /// @brief did we update PTR record for this lease?
+ /// @brief Reverse zone updated?
+ ///
+ /// Set true if the DNS PTR record for this lease has been updated.
bool fqdn_rev_;
- /// @brief Lease comments.
+ /// @brief Lease comments
///
/// Currently not used. It may be used for keeping comments made by the
/// system administrator.
std::string comments_;
- /// @todo: Add DHCPv4 failover related fields here
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const = 0;
+
+ /// @brief returns true if the lease is expired
+ /// @return true if the lease is expired
+ bool expired() const;
+
+};
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 : public Lease {
+ /// @brief Maximum size of a hardware address
+ static const size_t HWADDR_MAX = 20;
+
+ /// @brief Address extension
+ ///
+ /// It is envisaged that in some cases IPv4 address will be accompanied
+ /// with some additional data. One example of such use are Address + Port
+ /// solutions (or Port-restricted Addresses), where several clients may get
+ /// the same address, but different port ranges. This feature is not
+ /// expected to be widely used. Under normal circumstances, the value
+ /// should be 0.
+ uint32_t ext_;
+
+ /// @brief Hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ /// @brief Client identifier
+ ///
+ /// @todo Should this be a pointer to a client ID or the ID itself?
+ /// Compare with the DUID in the Lease6 structure.
+ ClientIdPtr client_id_;
/// @brief Constructor
///
+ /// @param addr IPv4 address as unsigned 32-bit integer in network byte
+ /// order.
+ /// @param hwaddr Hardware address buffer
+ /// @param hwaddr_len Length of hardware address buffer
+ /// @param clientid Client identification buffer
+ /// @param clientid_len Length of client identification buffer
+ /// @param valid_lft Lifetime of the lease
+ /// @param cltt Client last transmission time
+ /// @param subnet_id Subnet identification
+ Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len,
+ const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
+ uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
+ : Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
+ ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+ client_id_(new ClientId(clientid, clientid_len)) {
+ }
+
+ /// @brief Default constructor
+ ///
/// Initialize fields that don't have a default constructor.
- /// @todo Remove this
- Lease4() : addr_(0) {}
+ Lease4() : Lease(0, 0, 0, 0, 0, 0) {
+ }
+
+ /// @brief Compare two leases for equality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator==(const Lease4& other) const;
+
+ /// @brief Compare two leases for inequality
+ ///
+ /// @param other lease6 object with which to compare
+ bool operator!=(const Lease4& other) const {
+ return (!operator==(other));
+ }
+
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual represenation of lease data
+ virtual std::string toText() const;
+
+ /// @todo: Add DHCPv4 failover related fields here
};
/// @brief Pointer to a Lease4 structure.
typedef boost::shared_ptr<Lease4> Lease4Ptr;
/// @brief A collection of IPv4 leases.
-typedef std::vector< boost::shared_ptr<Lease4Ptr> > Lease4Collection;
+typedef std::vector<Lease4Ptr> Lease4Collection;
+
+
/// @brief Structure that holds a lease for IPv6 address and/or prefix
///
-/// For performance reasons it is a simple structure, not a class. Had we chose to
-/// make it a class, all fields would have to be made private and getters/setters
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
/// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access rather than through getters/setters is warranted.
-struct Lease6 {
+/// extensively, direct access is warranted.
+struct Lease6 : public Lease {
+
+ /// @brief Type of lease contents
typedef enum {
LEASE_IA_NA, /// the lease contains non-temporary IPv6 address
LEASE_IA_TA, /// the lease contains temporary IPv6 address
LEASE_IA_PD /// the lease contains IPv6 prefix (for prefix delegation)
} LeaseType;
- Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
- uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
- uint32_t t2, SubnetID subnet_id, uint8_t prefixlen_ = 0);
-
- /// @brief specifies lease type (normal addr, temporary addr, prefix)
+ /// @brief Lease type
+ ///
+ /// One of normal address, temporary address, or prefix.
LeaseType type_;
- /// IPv6 address
- isc::asiolink::IOAddress addr_;
-
- /// IPv6 prefix length (used only for PD)
+ /// @brief IPv6 prefix length
+ ///
+ /// This is used only for prefix delegations and is ignored otherwise.
uint8_t prefixlen_;
- /// @brief IAID
+ /// @brief Identity Association Identifier (IAID)
///
- /// Identity Association IDentifier. DHCPv6 stores all addresses and prefixes
- /// in IA containers (IA_NA, IA_TA, IA_PD). Most containers may appear more
- /// than once in a message. To differentiate between them, IAID field is present
+ /// DHCPv6 stores all addresses and prefixes in IA containers (IA_NA,
+ /// IA_TA, IA_PD). All containers may appear more than once in a message.
+ /// To differentiate between them, the IAID field is present
uint32_t iaid_;
- /// @brief client identifier
- boost::shared_ptr<DUID> duid_;
+ /// @brief Client identifier
+ DuidPtr duid_;
/// @brief preferred lifetime
///
- /// This parameter specifies preferred lifetime since the lease was assigned/renewed
- /// (cltt), expressed in seconds.
+ /// This parameter specifies the preferred lifetime since the lease was
+ /// assigned or renewed (cltt), expressed in seconds.
uint32_t preferred_lft_;
- /// @brief valid lifetime
- ///
- /// This parameter specified valid lifetime since the lease was assigned/renewed
- /// (cltt), expressed in seconds.
- uint32_t valid_lft_;
-
- /// @brief T1 timer
- ///
- /// Specifies renewal time. Although technically it is a property of IA container,
- /// not the address itself, since our data model does not define separate IA
- /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
- /// for the same IA, each must have consistent T1 and T2 values. Specified in
- /// seconds since cltt.
- /// This value will also be useful for failover to calculate the next expected
- /// client transmission time.
- uint32_t t1_;
-
- /// @brief T2 timer
- ///
- /// Specifies rebinding time. Although technically it is a property of IA container,
- /// not the address itself, since our data model does not define separate IA
- /// entity, we are keeping it in the lease. In case of multiple addresses/prefixes
- /// for the same IA, each must have consistent T1 and T2 values. Specified in
- /// seconds since cltt.
- uint32_t t2_;
-
- /// @brief client last transmission time
- ///
- /// Specifies a timestamp, when last transmission from a client was received.
- time_t cltt_;
-
- /// @brief Subnet identifier
- ///
- /// Specifies subnet-id of the subnet that the lease belongs to
- SubnetID subnet_id_;
-
- /// @brief Is this a fixed lease?
- ///
- /// Fixed leases are kept after they are released/expired.
- bool fixed_;
-
- /// @brief client hostname
- ///
- /// This field may be empty
- std::string hostname_;
-
- /// @brief did we update AAAA record for this lease?
- bool fqdn_fwd_;
-
- /// @brief did we update PTR record for this lease?
- bool fqdn_rev_;
-
- /// @brief Lease comments
- ///
- /// This field is currently not used.
- std::string comments_;
-
/// @todo: Add DHCPv6 failover related fields here
/// @brief Constructor
- ///
- /// Initialize fields that don't have a default constructor.
- Lease6() : addr_("::") {}
+ Lease6(LeaseType type, const isc::asiolink::IOAddress& addr, DuidPtr duid,
+ uint32_t iaid, uint32_t preferred, uint32_t valid, uint32_t t1,
+ uint32_t t2, SubnetID subnet_id, uint8_t prefixlen_ = 0);
- /// @brief Convert Lease6 to Printable Form
+ /// @brief Constructor
///
- /// @return String form of the lease
- std::string toText();
+ /// Initialize fields that don't have a default constructor.
+ Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0),
+ type_(LEASE_IA_NA) {
+ }
/// @brief Compare two leases for equality
///
@@ -312,12 +344,16 @@ struct Lease6 {
return (!operator==(other));
}
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const;
};
/// @brief Pointer to a Lease6 structure.
typedef boost::shared_ptr<Lease6> Lease6Ptr;
-/// @brief Const pointer to a Lease6 structure.
+/// @brief Pointer to a const Lease6 structure.
typedef boost::shared_ptr<const Lease6> ConstLease6Ptr;
/// @brief A collection of IPv6 leases.
@@ -335,9 +371,6 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// see the documentation of those classes for details.
class LeaseMgr {
public:
- /// Client Hardware address
- typedef std::vector<uint8_t> HWAddr;
-
/// Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
@@ -368,19 +401,6 @@ public:
/// with the same address was already there).
virtual bool addLease(const Lease6Ptr& lease) = 0;
- /// @brief Returns IPv4 lease for specified IPv4 address and subnet_id
- ///
- /// This method is used to get a lease for specific subnet_id. There can be
- /// at most one lease for any given subnet, so this method returns a single
- /// pointer.
- ///
- /// @param addr address of the searched lease
- /// @param subnet_id ID of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
- SubnetID subnet_id) const = 0;
-
/// @brief Returns an IPv4 lease for specified IPv4 address
///
/// This method return a lease that is associated with a given address.
@@ -405,7 +425,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -417,7 +437,7 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const = 0;
/// @brief Returns existing IPv4 lease for specified client-id
@@ -493,17 +513,11 @@ public:
/// @brief Deletes a lease.
///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease4(const isc::asiolink::IOAddress& addr) = 0;
-
- /// @brief Deletes a lease.
- ///
- /// @param addr IPv6 address of the lease to be deleted.
+ /// @param addr Address of the lease to be deleted. (This can be IPv4 or
+ /// IPv6.)
///
/// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease6(const isc::asiolink::IOAddress& addr) = 0;
+ virtual bool deleteLease(const isc::asiolink::IOAddress& addr) = 0;
/// @brief Return backend type
///
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 47c53ed..9fd276d 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -14,6 +14,7 @@
#include "config.h"
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#ifdef HAVE_MYSQL
@@ -43,17 +44,17 @@ LeaseMgrFactory::getLeaseMgrPtr() {
}
LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbconfig) {
+LeaseMgrFactory::parse(const std::string& dbaccess) {
LeaseMgr::ParameterMap mapped_tokens;
- if (!dbconfig.empty()) {
+ if (!dbaccess.empty()) {
vector<string> tokens;
// We need to pass a string to is_any_of, not just char*. Otherwise
// there are cryptic warnings on Debian6 running g++ 4.4 in
// /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
// array bounds"
- boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+ boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
BOOST_FOREACH(std::string token, tokens) {
size_t pos = token.find("=");
if (pos != string::npos) {
@@ -61,6 +62,7 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
string value = token.substr(pos + 1);
mapped_tokens.insert(make_pair(name, value));
} else {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
isc_throw(InvalidParameter, "Cannot parse " << token
<< ", expected format is name=value");
}
@@ -70,31 +72,70 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
return (mapped_tokens);
}
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+ // Reconstruct the access string: start of with an empty string, then
+ // work through all the parameters in the original string and add them.
+ std::string access;
+ for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens are preceded by a space.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter...
+ access += i->first;
+ access += "=";
+
+ // ... and the value, except in the case of the password, where a
+ // redacted value is appended.
+ if (i->first == std::string("password")) {
+ access += "*****";
+ } else {
+ access += i->second;
+ }
+ }
+
+ return (access);
+}
+
void
-LeaseMgrFactory::create(const std::string& dbconfig) {
+LeaseMgrFactory::create(const std::string& dbaccess) {
const std::string type = "type";
+ // Parse the access string and create a redacted string for logging.
+ LeaseMgr::ParameterMap parameters = parse(dbaccess);
+ std::string redacted = redactedAccessString(parameters);
+
// Is "type" present?
- LeaseMgr::ParameterMap parameters = parse(dbconfig);
if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
isc_throw(InvalidParameter, "Database configuration parameters do not "
"contain the 'type' keyword");
}
+
// Yes, check what it is.
#ifdef HAVE_MYSQL
if (parameters[type] == string("mysql")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
#endif
if (parameters[type] == string("memfile")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
return;
}
// Get here on no match
- isc_throw(InvalidType, "Database configuration parameter 'type' does "
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
"not specify a supported database backend");
}
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
index 338c941..9218cf5 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.h
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -66,21 +66,21 @@ public:
/// @note When called, the current lease manager is <b>always</b> destroyed
/// and a new one created - even if the parameters are the same.
///
- /// dbconfig is a generic way of passing parameters. Parameters are passed
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
/// in the "name=value" format, separated by spaces. The data MUST include
/// a keyword/value pair of the form "type=dbtype" giving the database
/// type, e.q. "mysql" or "sqlite3".
///
- /// @param dbconfig Database configuration parameters. These are in
- /// the form of "keyword=value" pairs, separated by spaces. These
- /// are back-end specific, although must include the "type" keyword
- /// which gives the backend in use.
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
///
- /// @throw isc::InvalidParameter dbconfig string does not contain the "type"
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
/// keyword.
- /// @throw isc::dhcp::InvalidType The "type" keyword in dbconfig does not
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
/// identify a supported backend.
- static void create(const std::string& dbconfig);
+ static void create(const std::string& dbaccess);
/// @brief Destroy lease manager
///
@@ -89,7 +89,7 @@ public:
/// lease manager is available.
static void destroy();
- /// @brief Return Current Lease Manager
+ /// @brief Return current lease manager
///
/// Returns an instance of the "current" lease manager. An exception
/// will be thrown if none is available.
@@ -98,15 +98,26 @@ public:
/// create() to create one before calling this method.
static LeaseMgr& instance();
- /// @brief Parse Database Parameters
+ /// @brief Parse database access string
///
/// Parses the string of "keyword=value" pairs and separates them
/// out into the map.
///
- /// @param dbconfig Database configuration string
+ /// @param dbaccess Database access string.
///
/// @return std::map<std::string, std::string> Map of keyword/value pairs.
- static LeaseMgr::ParameterMap parse(const std::string& dbconfig);
+ static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
+
+ /// @brief Redact database access string
+ ///
+ /// Takes the database parameters and returns a database access string
+ /// passwords replaced by asterisks. This string is used in log messages.
+ ///
+ /// @param parameters Database access parameters (output of "parse").
+ ///
+ /// @return Redacted database access string.
+ static std::string redactedAccessString(
+ const LeaseMgr::ParameterMap& parameters);
private:
/// @brief Hold pointer to lease manager
diff --git a/src/lib/dhcpsrv/libdhcpsrv.dox b/src/lib/dhcpsrv/libdhcpsrv.dox
new file mode 100644
index 0000000..bb4a8ec
--- /dev/null
+++ b/src/lib/dhcpsrv/libdhcpsrv.dox
@@ -0,0 +1,86 @@
+/**
+ @page libdhcpsrv libdhcpsrv - Server DHCP library
+
+This library contains code useful for DHCPv4 and DHCPv6 server operations, like
+Lease Manager that stores leases information, configuration manager that stores
+configuration etc. The code here is server specific. For generic (useful in
+server, client, relay and other tools like perfdhcp) code, please see
+\ref libdhcp.
+
+This library contains several crucial elements of the DHCP server operation:
+
+- isc::dhcp::LeaseMgr - Lease Manager is a name for database backend that stores
+ leases.
+- isc::dhcp::CfgMgr - Configuration Manager that holds DHCP specific
+ configuration information (subnets, pools, options, timer values etc.) in
+ easy to use format.
+- AllocEngine - allocation engine that handles new requestes and allocates new
+ leases.
+
+ at section leasemgr Lease Manager
+
+LeaseMgr provides a common, unified abstract API for all database backends. All
+backends are derived from the base class isc::dhcp::LeaseMgr. Currently the
+only available backend is MySQL (see \ref isc::dhcp::MySqlLeaseMgr).
+
+ at section cfgmgr Configuration Manager
+
+Configuration Manager (\ref isc::dhcp::CfgMgr) stores configuration information
+necessary for DHCPv4 and DHCPv6 server operation. In particular, it stores
+subnets (\ref isc::dhcp::Subnet4 and \ref isc::dhcp::Subnet6) together with
+their pools (\ref isc::dhcp::Pool4 and \ref isc::dhcp::Pool6), options and
+other information specified by the used in BIND10 configuration.
+
+ at section allocengine Allocation Engine
+
+Allocation Engine (\ref isc::dhcp::AllocEngine) is what its name say - an engine
+that handles allocation of new leases. It takes parameters that the client
+provided (client-id, DUID, subnet, a hint if the user provided one, etc.) and
+then attempts to allocate a lease.
+
+There is no single best soluction to the address assignment problem. Server
+is expected to pick an address from its available pools is currently not used.
+There are many possible algorithms that can do that, each with its own advantages
+and drawbacks. This allocation engine must provide robust operation is radically
+different scenarios, so there address selection problem was abstracted into
+separate module, called allocator. Its sole purpose is to pick an address from
+a pool. Allocation engine will then check if the picked address is free and if
+it is not, then will ask allocator to pick again.
+
+At lease 3 allocators will be implemented:
+
+- Iterative - it iterates over all addresses in available pools, one
+by one. The advantages of this approach are speed (typically it only needs to
+increase last address), the guarantee to cover all addresses and predictability.
+This allocator behaves very good in case of nearing depletion. Even when pools
+are almost completely allocated, it still will be able to allocate outstanding
+leases efficiently. Predictability can also be considered a serious flaw in
+some environments, as prediction of the next address is trivial and can be
+leveraged by an attacker. Another drawback of this allocator is that it does
+not attempt to give the same address to returning clients (clients that released
+or expired their leases and are requesting a new lease will likely to get a
+different lease). This allocator is implemented in \ref isc::dhcp::AllocEngine::IterativeAllocator.
+
+- Hashed - ISC-DHCP uses hash of the client-id or DUID to determine, which
+address is tried first. If that address is not available, the result is hashed
+again. That procedure is repeated until available address is found or there
+are no more addresses left. The benefit of that approach is that it provides
+a relative lease stability, so returning old clients are likely to get the same
+address again. The drawbacks are increased computation cost, as each iteration
+requires use of a hashing function. That is especially difficult when the
+pools are almost depleted. It also may be difficult to guarantee that the
+repeated hashing will iterate over all available addresses in all pools. Flawed
+hash algorithm can go into cycles that iterate over only part of the addresses.
+It is difficult to detect such issues as only some initial seed (client-id
+or DUID) values may trigger short cycles. This allocator is currently not
+implemented.
+
+- Random - Another possible approach to address selection is randomization. This
+allocator can pick an address randomly from the configured pool. The benefit
+of this approach is that it is easy to implement and makes attacks based on
+address prediction more difficult. The drawback of this approach is that
+returning clients are almost guaranteed to get a different address. Another
+drawback is that with almost depleted pools it is increasingly difficult to
+"guess" an address that is free. This allocator is currently not implemented.
+
+*/
\ No newline at end of file
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 8a59b73..34f21e9 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -12,7 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <exceptions/exceptions.h>
#include <iostream>
@@ -20,19 +22,29 @@ using namespace isc::dhcp;
Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
: LeaseMgr(parameters) {
- std::cout << "Warning: Using memfile database backend. It is usable for" << std::endl;
- std::cout << "Warning: limited testing only. File support not implemented yet." << std::endl;
- std::cout << "Warning: Leases will be lost after restart." << std::endl;
+ std::cout << "Warning: Using memfile database backend. It is usable for limited"
+ << " testing only. Leases will be lost after restart." << std::endl;
}
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
}
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
- return (false);
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
+
+ if (getLease4(lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+ storage4_.insert(lease);
+ return (true);
}
bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
if (getLease6(lease->addr_)) {
// there is a lease with specified address already
return (false);
@@ -41,35 +53,71 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
return (true);
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
- return (Lease4Ptr());
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ return (Lease4Ptr());
+ } else {
+ return (*l);
+ }
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
- return (Lease4Collection());
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
+
+ isc_throw(NotImplemented, "getLease4(HWaddr x) method not implemented yet");
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( ((*l)->hwaddr_ == hwaddr.hwaddr_) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+
+ // not found
return (Lease4Ptr());
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
- SubnetID) const {
- return (Lease4Ptr());
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
+ isc_throw(NotImplemented, "getLease4(ClientId) not implemented");
}
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+ .arg(client_id.toText());
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( (*(*l)->client_id_ == client_id) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
- SubnetID) const {
+ // not found
return (Lease4Ptr());
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
- return (Lease4Collection());
-}
+Lease6Ptr Memfile_LeaseMgr::getLease6(
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
return (Lease6Ptr());
@@ -78,12 +126,20 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) cons
}
}
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+
return (Lease6Collection());
}
Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
+
/// @todo: Slow, naive implementation. Write it using additional indexes
for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
if ( (*((*l)->duid_) == duid) &&
@@ -95,25 +151,43 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
return (Lease6Ptr());
}
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
}
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
-}
-bool Memfile_LeaseMgr::deleteLease4(const isc::asiolink::IOAddress&) {
- return (false);
}
-bool Memfile_LeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
- Lease6Storage::iterator l = storage6_.find(addr);
- if (l == storage6_.end()) {
- // no such lease
- return (false);
+bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
+ if (addr.isV4()) {
+ // v4 lease
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ storage4_.erase(l);
+ return (true);
+ }
+
} else {
- storage6_.erase(l);
- return (true);
+ // v6 lease
+ Lease6Storage::iterator l = storage6_.find(addr);
+ if (l == storage6_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ storage6_.erase(l);
+ return (true);
+ }
}
}
@@ -125,8 +199,11 @@ std::string Memfile_LeaseMgr::getDescription() const {
void
Memfile_LeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
}
void
Memfile_LeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ROLLBACK);
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index d9b40e5..81e5fe3 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MEMFILE_LEASE_MGR_H
#define MEMFILE_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index/indexed_by.hpp>
@@ -68,16 +69,6 @@ public:
/// @return a collection of leases
virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
- /// @brief Returns existing IPv4 lease for specific address and subnet
- ///
- /// @todo Not implemented yet
- /// @param addr address of the searched lease
- /// @param subnet_id ID of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
- SubnetID subnet_id) const;
-
/// @brief Returns existing IPv4 leases for specified hardware address.
///
/// @todo Not implemented yet
@@ -90,7 +81,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -176,17 +167,11 @@ public:
/// @brief Deletes a lease.
///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease4(const isc::asiolink::IOAddress& addr);
-
- /// @brief Deletes a lease.
- ///
- /// @param addr IPv4 address of the lease to be deleted.
+ /// @param addr Address of the lease to be deleted. (This can be IPv4 or
+ /// IPv6.)
///
/// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease6(const isc::asiolink::IOAddress& addr);
+ virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
/// @brief Return backend type
///
@@ -199,12 +184,11 @@ public:
/// @brief Returns backend name.
///
- /// As there is no variation, in this case we return the type of the
- /// backend.
+ /// For now, memfile can only store data in memory.
///
/// @return Name of the backend.
virtual std::string getName() const {
- return ("memfile");
+ return ("memory");
}
/// @brief Returns description of the backend.
@@ -242,12 +226,28 @@ protected:
// IPv6 address that are unique. That particular key is a member
// of the Lease6 structure, is of type IOAddress and can be accessed
// by doing &Lease6::addr_
- boost::multi_index::ordered_unique<
- boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_>
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
>
>
> Lease6Storage; // Let the whole contraption be called Lease6Storage.
+ typedef boost::multi_index_container< // this is a multi-index container...
+ Lease4Ptr, // it will hold shared_ptr to leases6
+ boost::multi_index::indexed_by< // and will be sorted by
+ // IPv6 address that are unique. That particular key is a member
+ // of the Lease6 structure, is of type IOAddress and can be accessed
+ // by doing &Lease6::addr_
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >
+ >
+ > Lease4Storage; // Let the whole contraption be called Lease6Storage.
+
+ /// @brief stores IPv4 leases
+ Lease4Storage storage4_;
+
+ /// @brief stores IPv6 leases
Lease6Storage storage6_;
};
@@ -255,4 +255,3 @@ protected:
}; // end of isc namespace
#endif // MEMFILE_LEASE_MGR
-
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index a3387ac..292df61 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -15,12 +15,17 @@
#include <config.h>
#include <asiolink/io_address.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_lease_mgr.h>
+#include <boost/static_assert.hpp>
#include <mysql/mysqld_error.h>
#include <iostream>
#include <iomanip>
+#include <sstream>
#include <string>
#include <time.h>
@@ -28,35 +33,122 @@ using namespace isc;
using namespace isc::dhcp;
using namespace std;
+/// @file
+///
+/// This file holds the implementation of the Lease Manager using MySQL. The
+/// implementation uses MySQL's C API, as it comes as standard with the MySQL
+/// client libraries.
+///
+/// In general, each of the database access methods corresponds to one SQL
+/// statement. To avoid the overhead of parsing a statement every time it is
+/// used, when the database is opened "prepared statements" are created -
+/// essentially doing the SQL parsing up front. Every time a method is used
+/// to access data, the corresponding prepared statement is referenced. Each
+/// prepared statement contains a set of placeholders for data, each
+/// placeholder being for:
+///
+/// - data being added to the database (as in adding or updating a lease)
+/// - data being retrieved from the database (as in getting lease information)
+/// - selection criteria used to determine which records to update/retrieve.
+///
+/// All such data is associated with the prepared statment using an array of
+/// MYSQL_BIND structures. Each element in the array corresponds to one
+/// parameter in the prepared statement - the first element in the array is
+/// associated with the first parameter, the second element with the second
+/// parameter etc.
+///
+/// Within this file, the setting up of the MYSQL_BIND arrays for data being
+/// passed to and retrieved from the database is handled in the
+/// isc::dhcp::MySqlLease4Exchange and isc::dhcp::MySqlLease6Exchange classes.
+/// The classes also hold intermediate variables required for exchanging some
+/// of the data.
+///
+/// With these exchange objects in place, many of the methods follow similar
+/// logic:
+/// - Set up the MYSQL_BIND array for data being transferred to/from the
+/// database. For data being transferred to the database, some of the
+/// data is extracted from the lease to intermediate variables, whilst
+/// in other cases the MYSQL_BIND arrays point to the data in the lease.
+/// - Set up the MYSQL_BIND array for the data selection parameters.
+/// - Bind these arrays to the prepared statement.
+/// - Execute the statement.
+/// - If there is output, copy the data from the bound variables to the output
+/// lease object.
+
namespace {
///@{
-/// @brief Maximum Size of Database Fields
+
+/// @brief Maximum size of database fields
///
/// The following constants define buffer sizes for variable length database
/// fields. The values should be greater than or equal to the length set in
/// the schema definition.
///
-/// The exception is the length of any VARCHAR fields: these should be set
-/// greater than or equal to the length of the field plus 2: this allows for
-/// the insertion of a trailing null regardless of whether the data returned
-/// contains a trailing null (the documentation is not clear on this point).
+/// The exception is the length of any VARCHAR fields: buffers for these should
+/// be set greater than or equal to the length of the field plus 1: this allows
+/// for the insertion of a trailing null whatever data is returned.
+
+/// @brief Maximum size of an IPv6 address represented as a text string.
+///
+/// This is 32 hexadecimal characters written in 8 groups of four, plus seven
+/// colon separators.
+const size_t ADDRESS6_TEXT_MAX_LEN = 39;
+
+/// @brief Maximum size of a hardware address.
+const size_t HWADDR_MAX_LEN = 20;
+
+/// @brief MySQL True/False constants
+///
+/// Declare typed values so as to avoid problems of data conversion. These
+/// are local to the file but are given the prefix MLM (MySql Lease Manager) to
+/// avoid any likely conflicts with variables in header files named TRUE or
+/// FALSE.
+
+const my_bool MLM_FALSE = 0; ///< False value
+const my_bool MLM_TRUE = 1; ///< True value
-const size_t ADDRESS6_TEXT_MAX_LEN = 42; // Max size of a IPv6 text buffer
-const size_t DUID_MAX_LEN = 128; // Max size of a DUID
///@}
/// @brief MySQL Selection Statements
///
-/// Each statement is associated with the index, used by the various methods
-/// to access the prepared statement.
+/// Each statement is associated with an index, which is used to reference the
+/// associated prepared statement.
+
struct TaggedStatement {
MySqlLeaseMgr::StatementIndex index;
const char* text;
};
TaggedStatement tagged_statements[] = {
+ {MySqlLeaseMgr::DELETE_LEASE4,
+ "DELETE FROM lease4 WHERE address = ?"},
{MySqlLeaseMgr::DELETE_LEASE6,
"DELETE FROM lease6 WHERE address = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_ADDR,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id "
+ "FROM lease4 "
+ "WHERE address = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_CLIENTID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id "
+ "FROM lease4 "
+ "WHERE client_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_CLIENTID_SUBID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id "
+ "FROM lease4 "
+ "WHERE client_id = ? AND subnet_id = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_HWADDR,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id "
+ "FROM lease4 "
+ "WHERE hwaddr = ?"},
+ {MySqlLeaseMgr::GET_LEASE4_HWADDR_SUBID,
+ "SELECT address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id "
+ "FROM lease4 "
+ "WHERE hwaddr = ? AND subnet_id = ?"},
{MySqlLeaseMgr::GET_LEASE6_ADDR,
"SELECT address, duid, valid_lifetime, "
"expire, subnet_id, pref_lifetime, "
@@ -77,11 +169,20 @@ TaggedStatement tagged_statements[] = {
"WHERE duid = ? AND iaid = ? AND subnet_id = ?"},
{MySqlLeaseMgr::GET_VERSION,
"SELECT version, minor FROM schema_version"},
+ {MySqlLeaseMgr::INSERT_LEASE4,
+ "INSERT INTO lease4(address, hwaddr, client_id, "
+ "valid_lifetime, expire, subnet_id) "
+ "VALUES (?, ?, ?, ?, ?, ?)"},
{MySqlLeaseMgr::INSERT_LEASE6,
"INSERT INTO lease6(address, duid, valid_lifetime, "
"expire, subnet_id, pref_lifetime, "
"lease_type, iaid, prefix_len) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"},
+ {MySqlLeaseMgr::UPDATE_LEASE4,
+ "UPDATE lease4 SET address = ?, hwaddr = ?, "
+ "client_id = ?, valid_lifetime = ?, expire = ?, "
+ "subnet_id = ? "
+ "WHERE address = ?"},
{MySqlLeaseMgr::UPDATE_LEASE6,
"UPDATE lease6 SET address = ?, duid = ?, "
"valid_lifetime = ?, expire = ?, subnet_id = ?, "
@@ -94,9 +195,306 @@ TaggedStatement tagged_statements[] = {
}; // Anonymous namespace
+
+
namespace isc {
namespace dhcp {
+/// @brief Common MySQL and Lease Data Methods
+///
+/// The MySqlLease4Exchange and MySqlLease6Exchange classes provide the
+/// functionaility to set up binding information between variables in the
+/// program and data extracted from the database. This class is the common
+/// base to both of them, containing some common methods.
+
+class MySqlLeaseExchange {
+public:
+ /// @brief Set error indicators
+ ///
+ /// Sets the error indicator for each of the MYSQL_BIND elements. It points
+ /// the "error" field within an element of the input array to the
+ /// corresponding element of the passed error array.
+ ///
+ /// @param bind Array of BIND elements
+ /// @param error Array of error elements. If there is an error in getting
+ /// data associated with one of the "bind" elements, the
+ /// corresponding element in the error array is set to MLM_TRUE.
+ /// @param count Size of each of the arrays.
+ void setErrorIndicators(MYSQL_BIND* bind, my_bool* error, size_t count) {
+ for (size_t i = 0; i < count; ++i) {
+ error[i] = MLM_FALSE;
+ bind[i].error = reinterpret_cast<char*>(&error[i]);
+ }
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @param error Array of error elements. An element is set to MLM_TRUE
+ /// if the corresponding column in the database is the source of
+ /// the error.
+ /// @param names Array of column names, the same size as the error array.
+ /// @param count Size of each of the arrays.
+ std::string getColumnsInError(my_bool* error, std::string* names,
+ size_t count) {
+ std::string result = "";
+
+ // Accumulate list of column names
+ for (size_t i = 0; i < count; ++i) {
+ if (error[i] == MLM_TRUE) {
+ if (!result.empty()) {
+ result += ", ";
+ }
+ result += names[i];
+ }
+ }
+
+ if (result.empty()) {
+ result = "(None)";
+ }
+
+ return (result);
+ }
+};
+
+
+/// @brief Exchange MySQL and Lease4 Data
+///
+/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
+/// describe the parameters in the prepared statements. Where information is
+/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
+/// structure is identical. This class handles the creation of that array.
+///
+/// Owing to the MySQL API, the process requires some intermediate variables
+/// to hold things like data length etc. This object holds those variables.
+///
+/// @note There are no unit tests for this class. It is tested indirectly
+/// in all MySqlLeaseMgr::xxx4() calls where it is used.
+
+class MySqlLease4Exchange : public MySqlLeaseExchange {
+ /// @brief Set number of database columns for this lease structure
+ static const size_t LEASE_COLUMNS = 6;
+
+public:
+ /// @brief Constructor
+ ///
+ /// The initialization of the variables here is only to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlLease4Exchange() : addr4_(0), hwaddr_length_(0), client_id_length_(0) {
+ memset(hwaddr_buffer_, 0, sizeof(hwaddr_buffer_));
+ memset(client_id_buffer_, 0, sizeof(client_id_buffer_));
+ std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[0] = "address";
+ columns_[1] = "hwaddr";
+ columns_[2] = "client_id";
+ columns_[3] = "valid_lifetime";
+ columns_[4] = "expire";
+ columns_[5] = "subnet_id";
+ BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
+ }
+
+ /// @brief Create MYSQL_BIND objects for Lease4 Pointer
+ ///
+ /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
+ /// the database.
+ ///
+ /// @param lease Lease object to be added to the database. None of the
+ /// fields in the lease are modified - the lease data is only read.
+ ///
+ /// @return Vector of MySQL BIND objects representing the data to be added.
+ std::vector<MYSQL_BIND> createBindForSend(const Lease4Ptr& lease) {
+
+ // Store lease object to ensure it remains valid.
+ lease_ = lease;
+
+ // Initialize prior to constructing the array of MYSQL_BIND structures.
+ memset(bind_, 0, sizeof(bind_));
+
+ // Set up the structures for the various components of the lease4
+ // structure.
+
+ // Address: uint32_t
+ // The address in the Lease structure is an IOAddress object. Convert
+ // this to an integer for storage.
+ addr4_ = static_cast<uint32_t>(lease_->addr_);
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // hwaddr: varbinary(128)
+ // For speed, we avoid copying the data into temporary storage and
+ // instead extract it from the lease structure directly.
+ hwaddr_length_ = lease_->hwaddr_.size();
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(&(lease_->hwaddr_[0]));
+ bind_[1].buffer_length = hwaddr_length_;
+ bind_[1].length = &hwaddr_length_;
+
+ // client_id: varbinary(128)
+ client_id_ = lease_->client_id_->getClientId();
+ client_id_length_ = client_id_.size();
+ bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[2].buffer = reinterpret_cast<char*>(&client_id_[0]);
+ bind_[2].buffer_length = client_id_length_;
+ bind_[2].length = &client_id_length_;
+
+ // valid lifetime: unsigned int
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
+ bind_[3].is_unsigned = MLM_TRUE;
+
+ // expire: timestamp
+ // The lease structure holds the client last transmission time (cltt_)
+ // For convenience for external tools, this is converted to lease
+ // expiry time (expire). The relationship is given by:
+ //
+ // expire = cltt_ + valid_lft_
+ //
+ // @todo Handle overflows - a large enough valid_lft_ could cause
+ // an overflow on a 32-bit system.
+ MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
+ expire_);
+ bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[4].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[4].buffer_length = sizeof(expire_);
+
+ // subnet_id: unsigned int
+ // Can use lease_->subnet_id_ directly as it is of type uint32_t.
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Create BIND array to receive data
+ ///
+ /// Creates a MYSQL_BIND array to receive Lease4 data from the database.
+ /// After data is successfully received, getLeaseData() can be used to copy
+ /// it to a Lease6 object.
+ ///
+ std::vector<MYSQL_BIND> createBindForReceive() {
+
+ // Initialize MYSQL_BIND array.
+ memset(bind_, 0, sizeof(bind_));
+
+ // address: uint32_t
+ bind_[0].buffer_type = MYSQL_TYPE_LONG;
+ bind_[0].buffer = reinterpret_cast<char*>(&addr4_);
+ bind_[0].is_unsigned = MLM_TRUE;
+
+ // hwaddr: varbinary(20)
+ hwaddr_length_ = sizeof(hwaddr_buffer_);
+ bind_[1].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[1].buffer = reinterpret_cast<char*>(hwaddr_buffer_);
+ bind_[1].buffer_length = hwaddr_length_;
+ bind_[1].length = &hwaddr_length_;
+
+ // client_id: varbinary(128)
+ client_id_length_ = sizeof(client_id_buffer_);
+ bind_[2].buffer_type = MYSQL_TYPE_BLOB;
+ bind_[2].buffer = reinterpret_cast<char*>(client_id_buffer_);
+ bind_[2].buffer_length = client_id_length_;
+ bind_[2].length = &client_id_length_;
+
+ // lease_time: unsigned int
+ bind_[3].buffer_type = MYSQL_TYPE_LONG;
+ bind_[3].buffer = reinterpret_cast<char*>(&valid_lifetime_);
+ bind_[3].is_unsigned = MLM_TRUE;
+
+ // expire: timestamp
+ bind_[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
+ bind_[4].buffer = reinterpret_cast<char*>(&expire_);
+ bind_[4].buffer_length = sizeof(expire_);
+
+ // subnet_id: unsigned int
+ bind_[5].buffer_type = MYSQL_TYPE_LONG;
+ bind_[5].buffer = reinterpret_cast<char*>(&subnet_id_);
+ bind_[5].is_unsigned = MLM_TRUE;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(5 < LEASE_COLUMNS);
+
+ // Add the data to the vector. Note the end element is one after the
+ // end of the array.
+ return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
+ }
+
+ /// @brief Copy Received Data into Lease6 Object
+ ///
+ /// Called after the MYSQL_BIND array created by createBindForReceive()
+ /// has been used, this copies data from the internal member variables
+ /// into a Lease4 objec.
+ ///
+ /// @return Lease4Ptr Pointer to a Lease6 object holding the relevant
+ /// data.
+ Lease4Ptr getLeaseData() {
+ // Convert times received from the database to times for the lease
+ // structure
+ time_t cltt = 0;
+ MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+
+ // note that T1 and T2 are not stored
+ return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
+ client_id_buffer_, client_id_length_,
+ valid_lifetime_, 0, 0, cltt, subnet_id_)));
+ }
+
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
+ }
+
+private:
+
+ // Note: All array lengths are equal to the corresponding variable in the
+ // schema.
+ // Note: Arrays are declared fixed length for speed of creation
+ uint32_t addr4_; ///< IPv4 address
+ MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
+ std::string columns_[LEASE_COLUMNS];///< Column names
+ my_bool error_[LEASE_COLUMNS]; ///< Error array
+ std::vector<uint8_t> hwaddr_; ///< Hardware address
+ uint8_t hwaddr_buffer_[HWADDR_MAX_LEN];
+ ///< Hardware address buffer
+ unsigned long hwaddr_length_; ///< Hardware address length
+ std::vector<uint8_t> client_id_; ///< Client identification
+ uint8_t client_id_buffer_[ClientId::MAX_CLIENT_ID_LEN];
+ ///< Client ID buffer
+ unsigned long client_id_length_; ///< Client ID address length
+ MYSQL_TIME expire_; ///< Lease expiry time
+ Lease4Ptr lease_; ///< Pointer to lease object
+ uint32_t subnet_id_; ///< Subnet identification
+ uint32_t valid_lifetime_; ///< Lease time
+};
+
/// @brief Exchange MySQL and Lease6 Data
@@ -104,33 +502,45 @@ namespace dhcp {
/// On any MySQL operation, arrays of MYSQL_BIND structures must be built to
/// describe the parameters in the prepared statements. Where information is
/// inserted or retrieved - INSERT, UPDATE, SELECT - a large amount of that
-/// structure is identical - it defines data values in the Lease6 structure.
-/// This class handles the creation of that array.
+/// structure is identical. This class handles the creation of that array.
///
/// Owing to the MySQL API, the process requires some intermediate variables
-/// to hold things like length etc. This object holds the intermediate
-/// variables as well.
+/// to hold things like data length etc. This object holds those variables.
///
/// @note There are no unit tests for this class. It is tested indirectly
/// in all MySqlLeaseMgr::xxx6() calls where it is used.
-class MySqlLease6Exchange {
+class MySqlLease6Exchange : public MySqlLeaseExchange {
+ /// @brief Set number of database columns for this lease structure
+ static const size_t LEASE_COLUMNS = 9;
+
public:
/// @brief Constructor
///
- /// Apart from the initialization of false_ and true_, the initialization
- /// of addr6_length_, duid_length_, addr6_buffer_ and duid_buffer_ are
- /// to satisfy cppcheck: none are really needed, as all variables are
- /// initialized/set in the methods.
- MySqlLease6Exchange() : addr6_length_(0), duid_length_(0),
- false_(0), true_(1) {
+ /// The initialization of the variables here is nonly to satisfy cppcheck -
+ /// all variables are initialized/set in the methods before they are used.
+ MySqlLease6Exchange() : addr6_length_(0), duid_length_(0) {
memset(addr6_buffer_, 0, sizeof(addr6_buffer_));
memset(duid_buffer_, 0, sizeof(duid_buffer_));
+ std::fill(&error_[0], &error_[LEASE_COLUMNS], MLM_FALSE);
+
+ // Set the column names (for error messages)
+ columns_[0] = "address";
+ columns_[1] = "duid";
+ columns_[2] = "valid_lifetime";
+ columns_[3] = "expire";
+ columns_[4] = "subnet_id";
+ columns_[5] = "pref_lifetime";
+ columns_[6] = "lease_type";
+ columns_[7] = "iaid";
+ columns_[8] = "prefix_len";
+ BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
}
/// @brief Create MYSQL_BIND objects for Lease6 Pointer
///
- /// Fills in the MYSQL_BIND objects for the Lease6 passed to it.
+ /// Fills in the MYSQL_BIND array for sending data in the Lease4 object to
+ /// the database.
///
/// @param lease Lease object to be added to the database.
///
@@ -143,7 +553,7 @@ public:
// for this lease.
memset(bind_, 0, sizeof(bind_));
- // address: varchar(40)
+ // address: varchar(39)
addr6_ = lease_->addr_.toText();
addr6_length_ = addr6_.size();
@@ -154,13 +564,13 @@ public:
// is guaranteed to be valid until the next non-const operation on
// addr6_.)
//
- // Note that the const_cast could be avoided by copying the string to
- // a writeable buffer and storing the address of that in the "buffer"
- // element. However, this introduces a copy operation (with additional
- // overhead) purely to get round the strictures introduced by design of
- // the MySQL interface (which uses the area pointed to by "buffer" as
- // input when specifying query parameters and as output when retrieving
- // data). For that reason, "const_cast" has been used.
+ // The const_cast could be avoided by copying the string to a writeable
+ // buffer and storing the address of that in the "buffer" element.
+ // However, this introduces a copy operation (with additional overhead)
+ // purely to get round the structures introduced by design of the
+ // MySQL interface (which uses the area pointed to by "buffer" as input
+ // when specifying query parameters and as output when retrieving data).
+ // For that reason, "const_cast" has been used.
bind_[0].buffer_type = MYSQL_TYPE_STRING;
bind_[0].buffer = const_cast<char*>(addr6_.c_str());
bind_[0].buffer_length = addr6_length_;
@@ -177,8 +587,8 @@ public:
// valid lifetime: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
- bind_[2].buffer = reinterpret_cast<char*>(&lease->valid_lft_);
- bind_[2].is_unsigned = true_;
+ bind_[2].buffer = reinterpret_cast<char*>(&lease_->valid_lft_);
+ bind_[2].is_unsigned = MLM_TRUE;
// expire: timestamp
// The lease structure holds the client last transmission time (cltt_)
@@ -187,7 +597,7 @@ public:
//
// expire = cltt_ + valid_lft_
//
- // @TODO Handle overflows
+ // @todo Handle overflows
MySqlLeaseMgr::convertToDatabaseTime(lease_->cltt_, lease_->valid_lft_,
expire_);
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
@@ -198,36 +608,42 @@ public:
// Can use lease_->subnet_id_ directly as it is of type uint32_t.
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&lease_->subnet_id_);
- bind_[4].is_unsigned = true_;
+ bind_[4].is_unsigned = MLM_TRUE;
// pref_lifetime: unsigned int
// Can use lease_->preferred_lft_ directly as it is of type uint32_t.
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&lease_->preferred_lft_);
- bind_[5].is_unsigned = true_;
+ bind_[5].is_unsigned = MLM_TRUE;
// lease_type: tinyint
- // Must convert to uint8_t as lease_->type_ is a LeaseType variable
+ // Must convert to uint8_t as lease_->type_ is a LeaseType variable.
lease_type_ = lease_->type_;
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
- bind_[6].is_unsigned = true_;
+ bind_[6].is_unsigned = MLM_TRUE;
// iaid: unsigned int
// Can use lease_->iaid_ directly as it is of type uint32_t.
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&lease_->iaid_);
- bind_[7].is_unsigned = true_;
+ bind_[7].is_unsigned = MLM_TRUE;
// prefix_len: unsigned tinyint
// Can use lease_->prefixlen_ directly as it is uint32_t.
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&lease_->prefixlen_);
- bind_[8].is_unsigned = true_;
+ bind_[8].is_unsigned = MLM_TRUE;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
// Add the data to the vector. Note the end element is one after the
// end of the array.
- return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[9]));
+ return (std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
}
/// @brief Create BIND array to receive data
@@ -236,24 +652,22 @@ public:
/// After data is successfully received, getLeaseData() is used to copy
/// it to a Lease6 object.
///
- /// @return Vector of MySQL BIND objects.
+ /// @return Vector of MySQL BIND objects passed to the MySQL data retrieval
+ /// functions.
std::vector<MYSQL_BIND> createBindForReceive() {
- // Ensure both the array of MYSQL_BIND structures and the error array
- // are clear.
+ // Initialize MYSQL_BIND array.
memset(bind_, 0, sizeof(bind_));
- memset(error_, 0, sizeof(error_));
- // address: varchar
+ // address: varchar(39)
// A Lease6_ address has a maximum of 39 characters. The array is
- // a few bites longer than this to guarantee that we can always null
- // terminate it.
+ // one byte longer than this to guarantee that we can always null
+ // terminate it whatever is returned.
addr6_length_ = sizeof(addr6_buffer_) - 1;
bind_[0].buffer_type = MYSQL_TYPE_STRING;
bind_[0].buffer = addr6_buffer_;
bind_[0].buffer_length = addr6_length_;
bind_[0].length = &addr6_length_;
- bind_[0].error = &error_[0];
// client_id: varbinary(128)
duid_length_ = sizeof(duid_buffer_);
@@ -261,59 +675,57 @@ public:
bind_[1].buffer = reinterpret_cast<char*>(duid_buffer_);
bind_[1].buffer_length = duid_length_;
bind_[1].length = &duid_length_;
- bind_[1].error = &error_[1];
// lease_time: unsigned int
bind_[2].buffer_type = MYSQL_TYPE_LONG;
bind_[2].buffer = reinterpret_cast<char*>(&valid_lifetime_);
- bind_[2].is_unsigned = true_;
- bind_[2].error = &error_[2];
+ bind_[2].is_unsigned = MLM_TRUE;
// expire: timestamp
bind_[3].buffer_type = MYSQL_TYPE_TIMESTAMP;
bind_[3].buffer = reinterpret_cast<char*>(&expire_);
bind_[3].buffer_length = sizeof(expire_);
- bind_[3].error = &error_[3];
// subnet_id: unsigned int
bind_[4].buffer_type = MYSQL_TYPE_LONG;
bind_[4].buffer = reinterpret_cast<char*>(&subnet_id_);
- bind_[4].is_unsigned = true_;
- bind_[4].error = &error_[4];
+ bind_[4].is_unsigned = MLM_TRUE;
// pref_lifetime: unsigned int
bind_[5].buffer_type = MYSQL_TYPE_LONG;
bind_[5].buffer = reinterpret_cast<char*>(&pref_lifetime_);
- bind_[5].is_unsigned = true_;
- bind_[5].error = &error_[5];
+ bind_[5].is_unsigned = MLM_TRUE;
// lease_type: tinyint
bind_[6].buffer_type = MYSQL_TYPE_TINY;
bind_[6].buffer = reinterpret_cast<char*>(&lease_type_);
- bind_[6].is_unsigned = true_;
- bind_[6].error = &error_[6];
+ bind_[6].is_unsigned = MLM_TRUE;
// iaid: unsigned int
bind_[7].buffer_type = MYSQL_TYPE_LONG;
bind_[7].buffer = reinterpret_cast<char*>(&iaid_);
- bind_[7].is_unsigned = true_;
- bind_[7].error = &error_[7];
+ bind_[7].is_unsigned = MLM_TRUE;
// prefix_len: unsigned tinyint
bind_[8].buffer_type = MYSQL_TYPE_TINY;
bind_[8].buffer = reinterpret_cast<char*>(&prefixlen_);
- bind_[8].is_unsigned = true_;
- bind_[8].error = &error_[8];
+ bind_[8].is_unsigned = MLM_TRUE;
+
+ // Add the error flags
+ setErrorIndicators(bind_, error_, LEASE_COLUMNS);
+
+ // .. and check that we have the numbers correct at compile time.
+ BOOST_STATIC_ASSERT(8 < LEASE_COLUMNS);
// Add the data to the vector. Note the end element is one after the
// end of the array.
- return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[9]));
+ return(std::vector<MYSQL_BIND>(&bind_[0], &bind_[LEASE_COLUMNS]));
}
/// @brief Copy Received Data into Lease6 Object
///
/// Called after the MYSQL_BIND array created by createBindForReceive()
- /// has been used, this copies data from the internal member vairables
+ /// has been used, this copies data from the internal member variables
/// into a Lease6 object.
///
/// @return Lease6Ptr Pointer to a Lease6 object holding the relevant
@@ -321,74 +733,86 @@ public:
///
/// @throw isc::BadValue Unable to convert Lease Type value in database
Lease6Ptr getLeaseData() {
-
- // Create the object to be returned.
- Lease6Ptr result(new Lease6());
-
- // Put the data in the lease object
-
// The address buffer is declared larger than the buffer size passed
// to the access function so that we can always append a null byte.
+ // Create the IOAddress object corresponding to the received data.
addr6_buffer_[addr6_length_] = '\0';
std::string address = addr6_buffer_;
+ isc::asiolink::IOAddress addr(address);
- // Set the other data, converting time as needed.
- result->addr_ = isc::asiolink::IOAddress(address);
- result->duid_.reset(new DUID(duid_buffer_, duid_length_));
- MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_,
- result->cltt_);
- result->valid_lft_ = valid_lifetime_;
- result->subnet_id_ = subnet_id_;
- result->preferred_lft_ = pref_lifetime_;
-
- // We can't convert from a numeric value to an enum, hence:
+ // Set the lease type in a variable of the appropriate data type, which
+ // has been initialized with an arbitrary (but valid) value.
+ Lease6::LeaseType type = Lease6::LEASE_IA_NA;
switch (lease_type_) {
case Lease6::LEASE_IA_NA:
- result->type_ = Lease6::LEASE_IA_NA;
+ type = Lease6::LEASE_IA_NA;
break;
case Lease6::LEASE_IA_TA:
- result->type_ = Lease6::LEASE_IA_TA;
+ type = Lease6::LEASE_IA_TA;
break;
case Lease6::LEASE_IA_PD:
- result->type_ = Lease6::LEASE_IA_PD;
+ type = Lease6::LEASE_IA_PD;
break;
default:
isc_throw(BadValue, "invalid lease type returned (" <<
lease_type_ << ") for lease with address " <<
- result->addr_.toText() << ". Only 0, 1, or 2 "
- "are allowed.");
+ address << ". Only 0, 1, or 2 are allowed.");
}
- result->iaid_ = iaid_;
- result->prefixlen_ = prefixlen_;
+
+ // Set up DUID,
+ DuidPtr duid_ptr(new DUID(duid_buffer_, duid_length_));
+
+ // Create the lease and set the cltt (after converting from the
+ // expire time retrieved from the database).
+ Lease6Ptr result(new Lease6(type, addr, duid_ptr, iaid_,
+ pref_lifetime_, valid_lifetime_, 0, 0,
+ subnet_id_, prefixlen_));
+ time_t cltt = 0;
+ MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ result->cltt_ = cltt;
return (result);
}
+ /// @brief Return columns in error
+ ///
+ /// If an error is returned from a fetch (in particular, a truncated
+ /// status), this method can be called to get the names of the fields in
+ /// error. It returns a string comprising the names of the fields
+ /// separated by commas. In the case of there being no error indicators
+ /// set, it returns the string "(None)".
+ ///
+ /// @return Comma-separated list of columns in error, or the string
+ /// "(None)".
+ std::string getErrorColumns() {
+ return (getColumnsInError(error_, columns_, LEASE_COLUMNS));
+ }
+
private:
// Note: All array lengths are equal to the corresponding variable in the
// schema.
+ // Note: arrays are declared fixed length for speed of creation
std::string addr6_; ///< String form of address
- char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN]; ///< Character
+ char addr6_buffer_[ADDRESS6_TEXT_MAX_LEN + 1]; ///< Character
///< array form of V6 address
unsigned long addr6_length_; ///< Length of the address
- MYSQL_BIND bind_[9]; ///< Static array for speed of access
+ MYSQL_BIND bind_[LEASE_COLUMNS]; ///< Bind array
+ std::string columns_[LEASE_COLUMNS];///< Column names
std::vector<uint8_t> duid_; ///< Client identification
- uint8_t duid_buffer_[DUID_MAX_LEN]; ///< Buffer form of DUID
+ uint8_t duid_buffer_[DUID::MAX_DUID_LEN]; ///< Buffer form of DUID
unsigned long duid_length_; ///< Length of the DUID
- my_bool error_[9]; ///< For error reporting
+ my_bool error_[LEASE_COLUMNS]; ///< Error indicators
MYSQL_TIME expire_; ///< Lease expiry time
- const my_bool false_; ///< "false" for MySql
uint32_t iaid_; ///< Identity association ID
Lease6Ptr lease_; ///< Pointer to lease object
- uint32_t valid_lifetime_; ///< Lease time
uint8_t lease_type_; ///< Lease type
uint8_t prefixlen_; ///< Prefix length
uint32_t pref_lifetime_; ///< Preferred lifetime
uint32_t subnet_id_; ///< Subnet identification
- const my_bool true_; ///< "true_" for MySql
+ uint32_t valid_lifetime_; ///< Lease time
};
@@ -396,15 +820,16 @@ private:
///
/// When a MySQL statement is exected, to fetch the results the function
/// mysql_stmt_fetch() must be called. As well as getting data, this
-/// allocated internal state. Subsequent calls to mysql_stmt_fetch
-/// can be made, but when all the data is retrieved, mysql_stmt_free_result
-/// must be called to free up the resources allocated.
+/// allocates internal state. Subsequent calls to mysql_stmt_fetch can be
+/// made, but when all the data is retrieved, mysql_stmt_free_result must be
+/// called to free up the resources allocated.
///
/// Created prior to the first fetch, this class's destructor calls
/// mysql_stmt_free_result, so eliminating the need for an explicit release
-/// in the method using mysql_stmt_free_result. In this way, it guarantees
+/// in the method calling mysql_stmt_free_result. In this way, it guarantees
/// that the resources are released even if the MySqlLeaseMgr method concerned
/// exits via an exception.
+
class MySqlFreeResult {
public:
@@ -433,7 +858,7 @@ private:
};
-// MySqlLeaseMgr Methods
+// MySqlLeaseMgr Constructor and Destructor
MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
: LeaseMgr(parameters), mysql_(NULL) {
@@ -444,11 +869,14 @@ MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
isc_throw(DbOpenError, "unable to initialize MySQL");
}
- // Open the database
+ // Open the database.
openDatabase();
- // Enable autocommit. For maximum speed, the global parameter
- // innodb_flush_log_at_trx_commit should be set to 2.
+ // Enable autocommit. To avoid a flush to disk on every commit, the global
+ // parameter innodb_flush_log_at_trx_commit should be set to 2. This will
+ // cause the changes to be written to the log, but flushed to disk in the
+ // background every second. Setting the parameter to that value will speed
+ // up the system, but at the risk of losing data if the system crashes.
my_bool result = mysql_autocommit(mysql_, 1);
if (result != 0) {
isc_throw(DbOperationError, mysql_error(mysql_));
@@ -457,16 +885,17 @@ MySqlLeaseMgr::MySqlLeaseMgr(const LeaseMgr::ParameterMap& parameters)
// Prepare all statements likely to be used.
prepareStatements();
- // Create the exchange object for use in exchanging data between the
+ // Create the exchange objects for use in exchanging data between the
// program and the database.
+ exchange4_.reset(new MySqlLease4Exchange());
exchange6_.reset(new MySqlLease6Exchange());
}
MySqlLeaseMgr::~MySqlLeaseMgr() {
// Free up the prepared statements, ignoring errors. (What would we do
- // about them - we're destroying this object and are not really concerned
- // with errors on a database connection that it about to go away.)
+ // about them? We're destroying this object and are not really concerned
+ // with errors on a database connection that is about to go away.)
for (int i = 0; i < statements_.size(); ++i) {
if (statements_[i] != NULL) {
(void) mysql_stmt_close(statements_[i]);
@@ -495,7 +924,7 @@ void
MySqlLeaseMgr::convertToDatabaseTime(time_t cltt, uint32_t valid_lifetime,
MYSQL_TIME& expire) {
- // Calculate expiry time and convert to various date/time fields.
+ // Calculate expiry time.
// @TODO: handle overflows
time_t expire_time = cltt + valid_lifetime;
@@ -510,8 +939,8 @@ MySqlLeaseMgr::convertToDatabaseTime(time_t cltt, uint32_t valid_lifetime,
expire.hour = expire_tm.tm_hour;
expire.minute = expire_tm.tm_min;
expire.second = expire_tm.tm_sec;
- expire.second_part = 0; // No fractional seconds
- expire.neg = static_cast<my_bool>(0); // Not negative
+ expire.second_part = 0; // No fractional seconds
+ expire.neg = my_bool(0); // Not negative
}
void
@@ -535,6 +964,9 @@ MySqlLeaseMgr::convertFromDatabaseTime(const MYSQL_TIME& expire,
}
+
+// Open the database using the parameters passed to the constructor.
+
void
MySqlLeaseMgr::openDatabase() {
@@ -555,7 +987,6 @@ MySqlLeaseMgr::openDatabase() {
user = suser.c_str();
} catch (...) {
// No user. Fine, we'll use NULL
- ;
}
const char* password = NULL;
@@ -565,7 +996,6 @@ MySqlLeaseMgr::openDatabase() {
password = spassword.c_str();
} catch (...) {
// No password. Fine, we'll use NULL
- ;
}
const char* name = NULL;
@@ -579,8 +1009,11 @@ MySqlLeaseMgr::openDatabase() {
}
// Set options for the connection:
- // - automatic reconnection
- my_bool auto_reconnect = 1;
+ //
+ // Automatic reconnection: after a period of inactivity, the client will
+ // disconnect from the database. This option causes it to automatically
+ // reconnect when another operation is about to be done.
+ my_bool auto_reconnect = MLM_TRUE;
int result = mysql_options(mysql_, MYSQL_OPT_RECONNECT, &auto_reconnect);
if (result != 0) {
isc_throw(DbOpenError, "unable to set auto-reconnect option: " <<
@@ -596,7 +1029,7 @@ MySqlLeaseMgr::openDatabase() {
// changed and so the "affected rows" (retrievable from MySQL) is zero.
// This makes it hard to distinguish whether the UPDATE changed no rows
// because no row matching the WHERE clause was found, or because a
- // row was found by no data was altered.
+ // row was found but no data was altered.
MYSQL* status = mysql_real_connect(mysql_, host, user, password, name,
0, NULL, CLIENT_FOUND_ROWS);
if (status != mysql_) {
@@ -604,6 +1037,12 @@ MySqlLeaseMgr::openDatabase() {
}
}
+// Prepared statement setup. The textual form of an SQL statement is stored
+// in a vector of strings (text_statements_) and is used in the output of
+// error messages. The SQL statement is also compiled into a "prepared
+// statement" (stored in statements_), which avoids the overhead of compilation
+// during use. As prepared statements have resources allocated to them, the
+// class destructor explicitly destroys them.
void
MySqlLeaseMgr::prepareStatement(StatementIndex index, const char* text) {
@@ -617,11 +1056,10 @@ MySqlLeaseMgr::prepareStatement(StatementIndex index, const char* text) {
// All OK, so prepare the statement
text_statements_[index] = std::string(text);
-
statements_[index] = mysql_stmt_init(mysql_);
if (statements_[index] == NULL) {
isc_throw(DbOperationError, "unable to allocate MySQL prepared "
- "statement structure" << mysql_error(mysql_));
+ "statement structure, reason: " << mysql_error(mysql_));
}
int status = mysql_stmt_prepare(statements_[index], text, strlen(text));
@@ -648,21 +1086,13 @@ MySqlLeaseMgr::prepareStatements() {
}
}
+// Add leases to the database. The two public methods accept a lease object
+// (either V4 of V6), bind the contents to the appropriate prepared
+// statement, then call common code to execute the statement.
bool
-MySqlLeaseMgr::addLease(const Lease4Ptr& /* lease */) {
- isc_throw(NotImplemented, "MySqlLeaseMgr::addLease(const Lease4Ptr&) "
- "not implemented yet");
- return (false);
-}
-
-
-bool
-MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
- const StatementIndex stindex = INSERT_LEASE6;
-
- // Create the MYSQL_BIND array for the lease
- std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
+MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
+ std::vector<MYSQL_BIND>& bind) {
// Bind the parameters to the statement
int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
@@ -685,86 +1115,301 @@ MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
return (true);
}
+bool
+MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR4).arg(lease->addr_.toText());
-Lease4Ptr
-MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */,
- SubnetID /* subnet_id */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const IOAddress&, SubnetID) "
- "not implemented yet");
- return (Lease4Ptr());
+ // Create the MYSQL_BIND array for the lease
+ std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
+
+ // ... and drop to common code.
+ return (addLeaseCommon(INSERT_LEASE4, bind));
}
+bool
+MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_ADD_ADDR6).arg(lease->addr_.toText());
-Lease4Ptr
-MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& /* addr */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const IOAddress&) "
- "not implemented yet");
- return (Lease4Ptr());
+ // Create the MYSQL_BIND array for the lease
+ std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
+
+ // ... and drop to common code.
+ return (addLeaseCommon(INSERT_LEASE6, bind));
}
+// Extraction of leases from the database.
+//
+// All getLease() methods ultimately call getLeaseCollection(). This
+// binds the input parameters passed to it with the appropriate prepared
+// statement and executes the statement. It then gets the results from the
+// database. getlease() methods that expect a single result back call it
+// with the "single" parameter set true: this causes an exception to be
+// generated if multiple records can be retrieved from the result set. (Such
+// an occurrence either indicates corruption in the database, or that an
+// assumption that a query can only return a single record is incorrect.)
+// Methods that require a collection of records have "single" set to the
+// default value of false. The logic is the same for both Lease4 and Lease6
+// objects, so the code is templated.
+//
+// Methods that require a collection of objects access this method through
+// two interface methods (also called getLeaseCollection()). These are
+// short enough as to be defined in the header file: all they do is to supply
+// the appropriate MySqlLeaseXExchange object depending on the type of the
+// LeaseCollection objects passed to them.
+//
+// Methods that require a single object to be returned access the method
+// through two interface methods (called getLease()). As well as supplying
+// the appropriate exchange object, they convert between lease collection
+// holding zero or one leases into an appropriate Lease object.
+
+template <typename Exchange, typename LeaseCollection>
+void MySqlLeaseMgr::getLeaseCollection(StatementIndex stindex,
+ MYSQL_BIND* bind,
+ Exchange& exchange,
+ LeaseCollection& result,
+ bool single) const {
+
+ // Bind the selection parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[stindex], bind);
+ checkError(status, stindex, "unable to bind WHERE clause parameter");
-Lease4Collection
-MySqlLeaseMgr::getLease4(const HWAddr& /* hwaddr */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const HWAddr&) "
- "not implemented yet");
- return (Lease4Collection());
+ // Set up the MYSQL_BIND array for the data being returned and bind it to
+ // the statement.
+ std::vector<MYSQL_BIND> outbind = exchange->createBindForReceive();
+ status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]);
+ checkError(status, stindex, "unable to bind SELECT clause parameters");
+
+ // Execute the statement
+ status = mysql_stmt_execute(statements_[stindex]);
+ checkError(status, stindex, "unable to execute");
+
+ // Ensure that all the lease information is retrieved in one go to avoid
+ // overhead of going back and forth between client and server.
+ status = mysql_stmt_store_result(statements_[stindex]);
+ checkError(status, stindex, "unable to set up for storing all results");
+
+ // Set up the fetch "release" object to release resources associated
+ // with the call to mysql_stmt_fetch when this method exits, then
+ // retrieve the data.
+ MySqlFreeResult fetch_release(statements_[stindex]);
+ int count = 0;
+ while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) {
+ try {
+ result.push_back(exchange->getLeaseData());
+
+ } catch (const isc::BadValue& ex) {
+ // Rethrow the exception with a bit more data.
+ isc_throw(BadValue, ex.what() << ". Statement is <" <<
+ text_statements_[stindex] << ">");
+ }
+
+ if (single && (++count > 1)) {
+ isc_throw(MultipleRecords, "multiple records were found in the "
+ "database where only one was expected for query "
+ << text_statements_[stindex]);
+ }
+ }
+
+ // How did the fetch end?
+ if (status == 1) {
+ // Error - unable to fetch results
+ checkError(status, stindex, "unable to fetch results");
+ } else if (status == MYSQL_DATA_TRUNCATED) {
+ // Data truncated - throw an exception indicating what was at fault
+ isc_throw(DataTruncated, text_statements_[stindex]
+ << " returned truncated data: columns affected are "
+ << exchange->getErrorColumns());
+ }
+}
+
+
+void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease4Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" paraeter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease4Collection collection;
+ getLeaseCollection(stindex, bind, exchange4_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
+}
+
+
+void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease6Ptr& result) const {
+ // Create appropriate collection object and get all leases matching
+ // the selection criteria. The "single" paraeter is true to indicate
+ // that the called method should throw an exception if multiple
+ // matching records are found: this particular method is called when only
+ // one or zero matches is expected.
+ Lease6Collection collection;
+ getLeaseCollection(stindex, bind, exchange6_, collection, true);
+
+ // Return single record if present, else clear the lease.
+ if (collection.empty()) {
+ result.reset();
+ } else {
+ result = *collection.begin();
+ }
}
+// Basic lease access methods. Obtain leases from the database using various
+// criteria.
+
Lease4Ptr
-MySqlLeaseMgr::getLease4(const HWAddr& /* hwaddr */,
- SubnetID /* subnet_id */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const HWAddr&, SubnetID) "
- "not implemented yet");
- return (Lease4Ptr());
+MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR4).arg(addr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ uint32_t addr4 = static_cast<uint32_t>(addr);
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_ADDR, inbind, result);
+
+ return (result);
}
Lease4Collection
-MySqlLeaseMgr::getLease4(const ClientId& /* clientid */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const ClientID&) "
- "not implemented yet");
- return (Lease4Collection());
+MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_HWADDR).arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
+
+ // As "buffer" is "char*" - even though the data is being read - we need
+ // to cast away the "const"ness as well as reinterpreting the data as
+ // a "char*". (We could avoid the "const_cast" by copying the data to a
+ // local variable, but as the data is only being read, this introduces
+ // an unnecessary copy).
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(data);
+ inbind[0].buffer_length = hwaddr_length;
+ inbind[0].length = &hwaddr_length;
+
+ // Get the data
+ Lease4Collection result;
+ getLeaseCollection(GET_LEASE4_HWADDR, inbind, result);
+
+ return (result);
}
Lease4Ptr
-MySqlLeaseMgr::getLease4(const ClientId& /* clientid */,
- SubnetID /* subnet_id */) const {
- isc_throw(NotImplemented, "MySqlLeaseMgr::getLease4(const ClientID&, SubnetID) "
- "not implemented yet");
- return (Lease4Ptr());
+MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hwaddr.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ // As "buffer" is "char*" - even though the data is being read - we need
+ // to cast away the "const"ness as well as reinterpreting the data as
+ // a "char*". (We could avoid the "const_cast" by copying the data to a
+ // local variable, but as the data is only being read, this introduces
+ // an unnecessary copy).
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
+
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(data);
+ inbind[0].buffer_length = hwaddr_length;
+ inbind[0].length = &hwaddr_length;
+
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_HWADDR_SUBID, inbind, result);
+
+ return (result);
}
-// A convenience function used in the various getLease6() methods. It binds
-// the selection parameters to the prepared statement, and binds the variables
-// that will receive the data. These are stored in the MySqlLease6Exchange
-// object associated with the lease manager and converted to a Lease6 object
-// when retrieved.
-void
-MySqlLeaseMgr::bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const {
+Lease4Collection
+MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_CLIENTID).arg(clientid.toText());
- // Bind the input parameters to the statement
- int status = mysql_stmt_bind_param(statements_[stindex], inbind);
- checkError(status, stindex, "unable to bind WHERE clause parameter");
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[1];
+ memset(inbind, 0, sizeof(inbind));
- // Set up the SELECT clause
- std::vector<MYSQL_BIND> outbind = exchange6_->createBindForReceive();
+ std::vector<uint8_t> client_data = clientid.getClientId();
+ unsigned long client_data_length = client_data.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&client_data[0]);
+ inbind[0].buffer_length = client_data_length;
+ inbind[0].length = &client_data_length;
- // Bind the output parameters to the statement
- status = mysql_stmt_bind_result(statements_[stindex], &outbind[0]);
- checkError(status, stindex, "unable to bind SELECT caluse parameters");
+ // Get the data
+ Lease4Collection result;
+ getLeaseCollection(GET_LEASE4_CLIENTID, inbind, result);
- // Execute the statement
- status = mysql_stmt_execute(statements_[stindex]);
- checkError(status, stindex, "unable to execute");
+ return (result);
+}
+
+
+Lease4Ptr
+MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id).arg(clientid.toText());
+
+ // Set up the WHERE clause value
+ MYSQL_BIND inbind[2];
+ memset(inbind, 0, sizeof(inbind));
+
+ std::vector<uint8_t> client_data = clientid.getClientId();
+ unsigned long client_data_length = client_data.size();
+ inbind[0].buffer_type = MYSQL_TYPE_BLOB;
+ inbind[0].buffer = reinterpret_cast<char*>(&client_data[0]);
+ inbind[0].buffer_length = client_data_length;
+ inbind[0].length = &client_data_length;
+
+ inbind[1].buffer_type = MYSQL_TYPE_LONG;
+ inbind[1].buffer = reinterpret_cast<char*>(&subnet_id);
+ inbind[1].is_unsigned = MLM_TRUE;
+
+ // Get the data
+ Lease4Ptr result;
+ getLease(GET_LEASE4_CLIENTID_SUBID, inbind, result);
+
+ return (result);
}
Lease6Ptr
MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
- const StatementIndex stindex = GET_LEASE6_ADDR;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR6).arg(addr.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
@@ -780,39 +1425,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
inbind[0].buffer_length = addr6_length;
inbind[0].length = &addr6_length;
- // Bind the input parameters to the statement and bind the output
- // to fields in the exchange object, then execute the prepared statement.
- bind6AndExecute(stindex, inbind);
-
- // Fetch the data and set up the "release" object to release associated
- // resources when this method exits.
- MySqlFreeResult fetch_release(statements_[stindex]);
- int status = mysql_stmt_fetch(statements_[stindex]);
-
Lease6Ptr result;
- if (status == 0) {
- try {
- result = exchange6_->getLeaseData();
- } catch (const isc::BadValue& ex) {
- // Lease type is returned, to rethrow the exception with a bit
- // more data.
- isc_throw(BadValue, ex.what() << ". Statement is <" <<
- text_statements_[stindex] << ">");
- }
-
- // As the address is the primary key in the table, we can't return
- // two rows, so we don't bother checking whether multiple rows have
- // been returned.
-
- } else if (status == 1) {
- checkError(status, stindex, "unable to fetch results");
-
- } else {
- // @TODO Handle truncation
- // We are ignoring truncation for now, so the only other result is
- // no data was found. In that case, we return a null Lease6 structure.
- // This has already been set, so no action is needed.
- }
+ getLease(GET_LEASE6_ADDR, inbind, result);
return (result);
}
@@ -820,7 +1434,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Collection
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
- const StatementIndex stindex = GET_LEASE6_DUID_IAID;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_DUID).arg(iaid).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -834,7 +1449,7 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
// Note that the const_cast could be avoided by copying the DUID to
// a writeable buffer and storing the address of that in the "buffer"
// element. However, this introduces a copy operation (with additional
- // overhead) purely to get round the strictures introduced by design of
+ // overhead) purely to get round the structures introduced by design of
// the MySQL interface (which uses the area pointed to by "buffer" as
// input when specifying query parameters and as output when retrieving
// data). For that reason, "const_cast" has been used.
@@ -849,45 +1464,11 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
// IAID
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&iaid);
- inbind[1].is_unsigned = static_cast<my_bool>(1);
-
- // Bind the input parameters to the statement and bind the output
- // to fields in the exchange object, then execute the prepared statement.
- bind6AndExecute(stindex, inbind);
-
- // Ensure that all the lease information is retrieved in one go to avoid
- // overhead of going back and forth between client and server.
- int status = mysql_stmt_store_result(statements_[stindex]);
- checkError(status, stindex, "unable to set up for storing all results");
+ inbind[1].is_unsigned = MLM_TRUE;
- // Fetch the data. There could be multiple rows, so we need to iterate
- // until all data has been retrieved.
+ // ... and get the data
Lease6Collection result;
-
- // Set up the fetch "release" object to release resources associated
- // with the call to mysql_stmt_fetch when this method exits, then
- // retrieve the data.
- MySqlFreeResult fetch_release(statements_[stindex]);
- while ((status = mysql_stmt_fetch(statements_[stindex])) == 0) {
- try {
- Lease6Ptr lease = exchange6_->getLeaseData();
- result.push_back(lease);
-
- } catch (const isc::BadValue& ex) {
- // Rethrow the exception with a bit more data.
- isc_throw(BadValue, ex.what() << ". Statement is <" <<
- text_statements_[stindex] << ">");
- }
- }
-
- // How did the fetch end?
- if (status == 1) {
- // Error - unable to fecth results
- checkError(status, stindex, "unable to fetch results");
- } else if (status == MYSQL_DATA_TRUNCATED) {
- // @TODO Handle truncation
- ;
- }
+ getLeaseCollection(GET_LEASE6_DUID_IAID, inbind, result);
return (result);
}
@@ -896,7 +1477,9 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
- const StatementIndex stindex = GET_LEASE6_DUID_IAID_SUBID;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
@@ -915,58 +1498,73 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
// IAID
inbind[1].buffer_type = MYSQL_TYPE_LONG;
inbind[1].buffer = reinterpret_cast<char*>(&iaid);
- inbind[1].is_unsigned = static_cast<my_bool>(1);
+ inbind[1].is_unsigned = MLM_TRUE;
// Subnet ID
inbind[2].buffer_type = MYSQL_TYPE_LONG;
inbind[2].buffer = reinterpret_cast<char*>(&subnet_id);
- inbind[2].is_unsigned = static_cast<my_bool>(1);
+ inbind[2].is_unsigned = MLM_TRUE;
- // Bind the input parameters to the statement and bind the output
- // to fields in the exchange object, then execute the prepared statement.
- bind6AndExecute(stindex, inbind);
-
- // Fetch the data and set up the "release" object to release associated
- // resources when this method exits then retrieve the data.
Lease6Ptr result;
- MySqlFreeResult fetch_release(statements_[stindex]);
- int status = mysql_stmt_fetch(statements_[stindex]);
- if (status == 0) {
- try {
- result = exchange6_->getLeaseData();
+ getLease(GET_LEASE6_DUID_IAID_SUBID, inbind, result);
- // TODO: check for more than one row returned. At present, just
- // ignore the excess and take the first.
+ return (result);
+}
- } catch (const isc::BadValue& ex) {
- // Lease type is returned, to rethrow the exception with a bit
- // more data.
- isc_throw(BadValue, ex.what() << ". Statement is <" <<
- text_statements_[stindex] << ">");
- }
+// Update lease methods. These comprise common code that handles the actual
+// update, and type-specific methods that set up the parameters for the prepared
+// statement depending on the type of lease.
- // As the address is the primary key in the table, we can't return
- // two rows, so we don't bother checking whether multiple rows have
- // been returned.
+template <typename LeasePtr>
+void
+MySqlLeaseMgr::updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
+ const LeasePtr& lease) {
- } else if (status == 1) {
- checkError(status, stindex, "unable to fetch results");
+ // Bind the parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[stindex], bind);
+ checkError(status, stindex, "unable to bind parameters");
- } else {
- // @TODO Handle truncation
- // We are ignoring truncation for now, so the only other result is
- // no data was found. In that case, we return a null Lease6 structure.
- // This has already been set, so the action is a no-op.
- }
+ // Execute
+ status = mysql_stmt_execute(statements_[stindex]);
+ checkError(status, stindex, "unable to execute");
- return (result);
+ // See how many rows were affected. The statement should only update a
+ // single row.
+ int affected_rows = mysql_stmt_affected_rows(statements_[stindex]);
+ if (affected_rows == 0) {
+ isc_throw(NoSuchLease, "unable to update lease for address " <<
+ lease->addr_.toText() << " as it does not exist");
+ } else if (affected_rows > 1) {
+ // Should not happen - primary key constraint should only have selected
+ // one row.
+ isc_throw(DbOperationError, "apparently updated more than one lease "
+ "that had the address " << lease->addr_.toText());
+ }
}
void
-MySqlLeaseMgr::updateLease4(const Lease4Ptr& /* lease4 */) {
- isc_throw(NotImplemented, "MySqlLeaseMgr::updateLease4(const Lease4Ptr&) "
- "not implemented yet");
+MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ const StatementIndex stindex = UPDATE_LEASE4;
+
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
+ // Create the MYSQL_BIND array for the data being updated
+ std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
+
+ // Set up the WHERE clause and append it to the MYSQL_BIND array
+ MYSQL_BIND where;
+ memset(&where, 0, sizeof(where));
+
+ uint32_t addr4 = static_cast<uint32_t>(lease->addr_);
+ where.buffer_type = MYSQL_TYPE_LONG;
+ where.buffer = reinterpret_cast<char*>(&addr4);
+ where.is_unsigned = MLM_TRUE;
+ bind.push_back(where);
+
+ // Drop to common update code
+ updateLeaseCommon(stindex, &bind[0], lease);
}
@@ -974,6 +1572,9 @@ void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -992,68 +1593,66 @@ MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
where.length = &addr6_length;
bind.push_back(where);
- // Bind the parameters to the statement
- int status = mysql_stmt_bind_param(statements_[stindex], &bind[0]);
- checkError(status, stindex, "unable to bind parameters");
+ // Drop to common update code
+ updateLeaseCommon(stindex, &bind[0], lease);
+}
+
+// Delete lease methods. Similar to other groups of methods, these comprise
+// a per-type method that sets up the relevant MYSQL_BIND array (in this
+// case, a single method for both V4 and V6 addresses) and a common method that
+// handles the common processing.
+
+bool
+MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
+
+ // Bind the input parameters to the statement
+ int status = mysql_stmt_bind_param(statements_[stindex], bind);
+ checkError(status, stindex, "unable to bind WHERE clause parameter");
// Execute
status = mysql_stmt_execute(statements_[stindex]);
checkError(status, stindex, "unable to execute");
- // See how many rows were affected. The statement should only delete a
- // single row.
- int affected_rows = mysql_stmt_affected_rows(statements_[stindex]);
- if (affected_rows == 0) {
- isc_throw(NoSuchLease, "unable to update lease for address " <<
- addr6 << " as it does not exist");
- } else if (affected_rows > 1) {
- // Should not happen - primary key constraint should only have selected
- // one row.
- isc_throw(DbOperationError, "apparently updated more than one lease "
- "that had the address " << addr6);
- }
-}
-
-
-bool
-MySqlLeaseMgr::deleteLease4(const isc::asiolink::IOAddress& /* addr */) {
- isc_throw(NotImplemented, "MySqlLeaseMgr::deleteLease4(const IOAddress&) "
- "not implemented yet");
- return (false);
+ // See how many rows were affected. Note that the statement may delete
+ // multiple rows.
+ return (mysql_stmt_affected_rows(statements_[stindex]) > 0);
}
bool
-MySqlLeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
- const StatementIndex stindex = DELETE_LEASE6;
+MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_DELETE_ADDR).arg(addr.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
- std::string addr6 = addr.toText();
- unsigned long addr6_length = addr6.size();
+ if (addr.isV4()) {
+ uint32_t addr4 = static_cast<uint32_t>(addr);
- // See the earlier description of the use of "const_cast" when accessing
- // the address for an explanation of the reason.
- inbind[0].buffer_type = MYSQL_TYPE_STRING;
- inbind[0].buffer = const_cast<char*>(addr6.c_str());
- inbind[0].buffer_length = addr6_length;
- inbind[0].length = &addr6_length;
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
- // Bind the input parameters to the statement
- int status = mysql_stmt_bind_param(statements_[stindex], inbind);
- checkError(status, stindex, "unable to bind WHERE clause parameter");
+ return (deleteLeaseCommon(DELETE_LEASE4, inbind));
- // Execute
- status = mysql_stmt_execute(statements_[stindex]);
- checkError(status, stindex, "unable to execute");
+ } else {
+ std::string addr6 = addr.toText();
+ unsigned long addr6_length = addr6.size();
- // See how many rows were affected. Note that the statement may delete
- // multiple rows.
- return (mysql_stmt_affected_rows(statements_[stindex]) > 0);
+ // See the earlier description of the use of "const_cast" when accessing
+ // the address for an explanation of the reason.
+ inbind[0].buffer_type = MYSQL_TYPE_STRING;
+ inbind[0].buffer = const_cast<char*>(addr6.c_str());
+ inbind[0].buffer_length = addr6_length;
+ inbind[0].length = &addr6_length;
+
+ return (deleteLeaseCommon(DELETE_LEASE6, inbind));
+ }
}
+// Miscellaneous database methods.
std::string
MySqlLeaseMgr::getName() const {
@@ -1061,7 +1660,7 @@ MySqlLeaseMgr::getName() const {
try {
name = getParameter("name");
} catch (...) {
- ;
+ // Return an empty name
}
return (name);
}
@@ -1077,6 +1676,9 @@ std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion() const {
const StatementIndex stindex = GET_VERSION;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
uint32_t major; // Major version number
uint32_t minor; // Minor version number
@@ -1123,6 +1725,7 @@ MySqlLeaseMgr::getVersion() const {
void
MySqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
if (mysql_commit(mysql_) != 0) {
isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
}
@@ -1131,6 +1734,7 @@ MySqlLeaseMgr::commit() {
void
MySqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
if (mysql_rollback(mysql_) != 0) {
isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 1884da6..62f4435 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MYSQL_LEASE_MGR_H
#define MYSQL_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
@@ -27,18 +28,22 @@ namespace dhcp {
// Define the current database schema values
-const uint32_t CURRENT_VERSION_VERSION = 0;
-const uint32_t CURRENT_VERSION_MINOR = 1;
+const uint32_t CURRENT_VERSION_VERSION = 1;
+const uint32_t CURRENT_VERSION_MINOR = 0;
-// Forward declaration of the Lease6 exchange object. This class is defined
+// Forward declaration of the Lease exchange objects. These classes are defined
// in the .cc file.
+class MySqlLease4Exchange;
class MySqlLease6Exchange;
/// @brief MySQL Lease Manager
///
-/// This is a concrete API for the backend for the MySQL database.
+/// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL
+/// database. Use of this backend presupposes that a MySQL database is
+/// available and that the Kea schema has been created within it.
+
class MySqlLeaseMgr : public LeaseMgr {
public:
/// @brief Constructor
@@ -68,7 +73,7 @@ public:
/// @brief Destructor (closes database)
virtual ~MySqlLeaseMgr();
- /// @brief Adds an IPv4 lease.
+ /// @brief Adds an IPv4 lease
///
/// @param lease lease to be added
///
@@ -79,7 +84,7 @@ public:
/// failed.
virtual bool addLease(const Lease4Ptr& lease);
- /// @brief Adds an IPv6 lease.
+ /// @brief Adds an IPv6 lease
///
/// @param lease lease to be added
///
@@ -90,23 +95,10 @@ public:
/// failed.
virtual bool addLease(const Lease6Ptr& lease);
- /// @brief Return IPv4 lease for specified IPv4 address and subnet_id
- ///
- /// This method is used to get a lease for specific subnet_id. There can be
- /// at most one lease for any given subnet, so this method returns a single
- /// pointer.
- ///
- /// @param addr address of the sought lease
- /// @param subnet_id ID of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr,
- SubnetID subnet_id) const;
-
/// @brief Returns an IPv4 lease for specified IPv4 address
///
/// This method return a lease that is associated with a given address.
- /// For other query types (by hardware addr, by DUID) there can be
+ /// For other query types (by hardware addr, by Client ID) there can be
/// several leases in different subnets (e.g. for mobile clients that
/// got address in different subnets). However, for a single address
/// there can be only one lease, so this method returns a pointer to
@@ -115,6 +107,12 @@ public:
/// @param addr address of the searched lease
///
/// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress& addr) const;
@@ -128,7 +126,13 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -140,7 +144,13 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const;
/// @brief Returns existing IPv4 lease for specified client-id
@@ -153,6 +163,12 @@ public:
/// @param clientid client identifier
///
/// @return lease collection
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual Lease4Collection getLease4(const ClientId& clientid) const;
/// @brief Returns existing IPv4 lease for specified client-id
@@ -164,6 +180,12 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual Lease4Ptr getLease4(const ClientId& clientid,
SubnetID subnet_id) const;
@@ -179,6 +201,9 @@ public:
///
/// @throw isc::BadValue record retrieved from database had an invalid
/// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
virtual Lease6Ptr getLease6(const isc::asiolink::IOAddress& addr) const;
@@ -194,6 +219,14 @@ public:
/// @param iaid IA identifier
///
/// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual Lease6Collection getLease6(const DUID& duid,
uint32_t iaid) const;
@@ -204,18 +237,35 @@ public:
/// @param subnet_id subnet id of the subnet the lease belongs to
///
/// @return smart pointer to the lease (or NULL if a lease is not found)
+ ///
+ /// @throw isc::BadValue record retrieved from database had an invalid
+ /// lease type field.
+ /// @throw isc::dhcp::DataTruncation Data was truncated on retrieval to
+ /// fit into the space allocated for the result. This indicates a
+ /// programming error.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual Lease6Ptr getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const;
/// @brief Updates IPv4 lease.
///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
/// @param lease4 The lease to be updated.
///
- /// If no such lease is present, an exception will be thrown.
+ /// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
+ /// exist.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
virtual void updateLease4(const Lease4Ptr& lease4);
/// @brief Updates IPv6 lease.
///
+ /// Updates the record of the lease in the database (as identified by the
+ /// address) with the data in the passed lease object.
+ ///
/// @param lease6 The lease to be updated.
///
/// @throw isc::dhcp::NoSuchLease Attempt to update a lease that did not
@@ -224,22 +274,16 @@ public:
/// failed.
virtual void updateLease6(const Lease6Ptr& lease6);
- /// @brief Deletes an IPv4 lease.
+ /// @brief Deletes a lease.
///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease4(const isc::asiolink::IOAddress& addr);
-
- /// @brief Deletes an IPv6 lease.
- ///
- /// @param addr IPv6 address of the lease to be deleted.
+ /// @param addr Address of the lease to be deleted. This can be an IPv4
+ /// address or an IPv6 address.
///
/// @return true if deletion was successful, false if no such lease exists
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- virtual bool deleteLease6(const isc::asiolink::IOAddress& addr);
+ virtual bool deleteLease(const isc::asiolink::IOAddress& addr);
/// @brief Return backend type
///
@@ -343,12 +387,20 @@ public:
///
/// The contents of the enum are indexes into the list of SQL statements
enum StatementIndex {
+ DELETE_LEASE4, // Delete from lease4 by address
DELETE_LEASE6, // Delete from lease6 by address
+ GET_LEASE4_ADDR, // Get lease4 by address
+ GET_LEASE4_CLIENTID, // Get lease4 by client ID
+ GET_LEASE4_CLIENTID_SUBID, // Get lease4 by client ID & subnet ID
+ GET_LEASE4_HWADDR, // Get lease4 by HW address
+ GET_LEASE4_HWADDR_SUBID, // Get lease4 by HW address & subnet ID
GET_LEASE6_ADDR, // Get lease6 by address
GET_LEASE6_DUID_IAID, // Get lease6 by DUID and IAID
- GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and Subnet ID
+ GET_LEASE6_DUID_IAID_SUBID, // Get lease6 by DUID, IAID and subnet ID
GET_VERSION, // Obtain version number
+ INSERT_LEASE4, // Add entry to lease4 table
INSERT_LEASE6, // Add entry to lease6 table
+ UPDATE_LEASE4, // Update a Lease4 entry
UPDATE_LEASE6, // Update a Lease6 entry
NUM_STATEMENTS // Number of statements
};
@@ -389,25 +441,150 @@ private:
/// @throw DbOpenError Error opening the database
void openDatabase();
- /// @brief Binds Parameters and Executes
+ /// @brief Add Lease Common Code
+ ///
+ /// This method performs the common actions for both flavours (V4 and V6)
+ /// of the addLease method. It binds the contents of the lease object to
+ /// the prepared statement and adds it to the database.
///
- /// This method abstracts a lot of common processing from the getXxxx()
- /// methods. It binds the parameters passed to it to the appropriate
- /// prepared statement, and binds the variables in the exchange6 object to
- /// the output parameters of the statement. It then executes the prepared
- /// statement.
+ /// @param stindex Index of statemnent being executed
+ /// @param bind MYSQL_BIND array that has been created for the type
+ /// of lease in question.
///
- /// The data can be retrieved using mysql_stmt_fetch and the getLeaseData()
- /// method on the exchange6 object.
+ /// @return true if the lease was added, false if it was not added because
+ /// a lease with that address already exists in the database.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ bool addLeaseCommon(StatementIndex stindex, std::vector<MYSQL_BIND>& bind);
+
+ /// @brief Get Lease Collection Common Code
+ ///
+ /// This method performs the common actions for obtaining multiple leases
+ /// from the database.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param exchange Exchange object to use
+ /// @param lease LeaseCollection object returned. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ /// @param single If true, only a single data item is to be retrieved.
+ /// If more than one is present, a MultipleRecords exception will
+ /// be thrown.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ template <typename Exchange, typename LeaseCollection>
+ void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ Exchange& exchange, LeaseCollection& result,
+ bool single = false) const;
+
+ /// @brief Get Lease Collection
+ ///
+ /// Gets a collection of Lease4 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param lease LeaseCollection object returned. Note that any leases in
+ /// the collection when this method is called are not erased: the
+ /// new data is appended to the end.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease4Collection& result) const {
+ getLeaseCollection(stindex, bind, exchange4_, result);
+ }
+
+ /// @brief Get Lease Collection
+ ///
+ /// Gets a collection of Lease6 objects. This is just an interface to
+ /// the get lease collection common code.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param lease LeaseCollection object returned. Note that any existing
+ /// data in the collection is erased first.
+ ///
+ /// @throw isc::dhcp::BadValue Data retrieved from the database was invalid.
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ /// @throw isc::dhcp::MultipleRecords Multiple records were retrieved
+ /// from the database where only one was expected.
+ void getLeaseCollection(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease6Collection& result) const {
+ getLeaseCollection(stindex, bind, exchange6_, result);
+ }
+
+ /// @brief Get Lease4 Common Code
+ ///
+ /// This method performs the common actions for the various getLease4()
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieveing only a single lease.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param lease Lease4 object returned
+ void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease4Ptr& result) const;
+
+ /// @brief Get Lease6 Common Code
+ ///
+ /// This method performs the common actions for the various getLease46)
+ /// methods. It acts as an interface to the getLeaseCollection() method,
+ /// but retrieveing only a single lease.
+ ///
+ /// @param stindex Index of statement being executed
+ /// @param bind MYSQL_BIND array for input parameters
+ /// @param lease Lease6 object returned
+ void getLease(StatementIndex stindex, MYSQL_BIND* bind,
+ Lease6Ptr& result) const;
+
+ /// @brief Update lease common code
+ ///
+ /// Holds the common code for updating a lease. It binds the parameters
+ /// to the prepared statement, executes it, then checks how many rows
+ /// were affected.
///
/// @param stindex Index of prepared statement to be executed
- /// @param inbind Array of MYSQL_BIND objects representing the parameters.
+ /// @param bind Array of MYSQL_BIND objects representing the parameters.
/// (Note that the number is determined by the number of parameters
/// in the statement.)
+ /// @param lease Pointer to the lease object whose record is being updated.
///
+ /// @throw NoSuchLease Could not update a lease because no lease matches
+ /// the address given.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- void bind6AndExecute(StatementIndex stindex, MYSQL_BIND* inbind) const;
+ template <typename LeasePtr>
+ void updateLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind,
+ const LeasePtr& lease);
+
+ /// @brief Delete lease common code
+ ///
+ /// Holds the common code for deleting a lease. It binds the parameters
+ /// to the prepared statement, executes the statement and checks to
+ /// see how many rows were deleted.
+ ///
+ /// @param stindex Index of prepared statement to be executed
+ /// @param bind Array of MYSQL_BIND objects representing the parameters.
+ /// (Note that the number is determined by the number of parameters
+ /// in the statement.)
+ ///
+ /// @return true if one or more rows were deleted, false if none were
+ /// deleted.
+ ///
+ /// @throw isc::dhcp::DbOperationError An operation on the open database has
+ /// failed.
+ bool deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind);
/// @brief Check Error and Throw Exception
///
@@ -433,14 +610,15 @@ private:
// Members
- /// Used for transfer of data to/from the database. This is a pointed-to
- /// object as its contents may change in "const" calls, while the rest
- /// of this object does not. (At alternative would be to declare it as
- /// "mutable".)
- boost::scoped_ptr<MySqlLease6Exchange> exchange6_;
+ /// The exchange objects are used for transfer of data to/from the database.
+ /// They are pointed-to objects as the contents may change in "const" calls,
+ /// while the rest of this object does not. (At alternative would be to
+ /// declare them as "mutable".)
+ boost::scoped_ptr<MySqlLease4Exchange> exchange4_; ///< Exchange object
+ boost::scoped_ptr<MySqlLease6Exchange> exchange6_; ///< Exchange object
MYSQL* mysql_; ///< MySQL context object
- std::vector<std::string> text_statements_; ///< Raw text of statements
std::vector<MYSQL_STMT*> statements_; ///< Prepared statements
+ std::vector<std::string> text_statements_; ///< Raw text of statements
};
}; // end of isc::dhcp namespace
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
new file mode 100644
index 0000000..0e802a7
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+ : name_(name), vendor_space_(vendor_space) {
+ // Check that provided option space name is valid.
+ if (!validateName(name_)) {
+ isc_throw(InvalidOptionSpace, "Invalid option space name "
+ << name_);
+ }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+ using namespace boost::algorithm;
+
+ // Allowed characters are: lower or upper case letters, digits,
+ // underscores and hyphens. Empty option spaces are not allowed.
+ if (all(name, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) &&
+ !name.empty() &&
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+ !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
+ return (true);
+
+ }
+ return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+ : OptionSpace(name),
+ enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+ const uint32_t enterprise_number)
+ : OptionSpace(name, true),
+ enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+ enterprise_number_ = enterprise_number;
+ OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/option_space.h b/src/lib/dhcpsrv/option_space.h
new file mode 100644
index 0000000..9eebd76
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_H
+#define OPTION_SPACE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+ InvalidOptionSpace(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param name option space name.
+ /// @param vendor_space boolean value that indicates that the object
+ /// describes the vendor specific option space.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref validateName function to check that the specified name is
+ /// correct.
+ OptionSpace(const std::string& name, const bool vendor_space = false);
+
+ /// @brief Return option space name.
+ ///
+ /// @return option space name.
+ const std::string& getName() const { return (name_); }
+
+ /// @brief Mark option space as non-vendor space.
+ void clearVendorSpace() {
+ vendor_space_ = false;
+ }
+
+ /// @brief Check if option space is vendor specific.
+ ///
+ /// @return boolean value that indicates if the object describes
+ /// the vendor specific option space.
+ bool isVendorSpace() const { return (vendor_space_); }
+
+ /// @brief Mark option space as vendor specific.
+ void setVendorSpace() {
+ vendor_space_ = true;
+ }
+
+ /// @brief Checks that the provided option space name is valid.
+ ///
+ /// It is expected that option space name consists of upper or
+ /// lower case letters or digits. Also, it may contain underscores
+ /// or dashes. Other characters are prohibited. The empty option
+ /// space names are invalid.
+ ///
+ /// @param name option space name to be validated.
+ ///
+ /// @return true if the option space is valid, else it returns false.
+ static bool validateName(const std::string& name);
+
+private:
+ std::string name_; ///< Holds option space name.
+
+ bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC3315.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+ /// @brief Constructor for non-vendor-specific options.
+ ///
+ /// This constructor marks option space as non-vendor specific.
+ ///
+ /// @param name option space name.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name);
+
+ /// @brief Constructor for vendor-specific options.
+ ///
+ /// This constructor marks option space as vendor specific and sets
+ /// enterprise number to a given value.
+ ///
+ /// @param name option space name.
+ /// @param enterprise_number enterprise number.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+ /// @brief Return enterprise number for the option space.
+ ///
+ /// @return enterprise number.
+ uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+ /// @brief Mark option space as vendor specific.
+ ///
+ /// @param enterprise_number enterprise number.
+ void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+
+ uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
new file mode 100644
index 0000000..f90bedd
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types in
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+template<typename ContainerType, typename ItemType>
+class OptionSpaceContainer {
+public:
+
+ /// Pointer to the container.
+ typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+ /// @brief Adds a new item to the option_space.
+ ///
+ /// @param item reference to the item being added.
+ /// @param name of the option space.
+ void addItem(const ItemType& item, const std::string& option_space) {
+ ItemsContainerPtr items = getItems(option_space);
+ items->push_back(item);
+ option_space_map_[option_space] = items;
+ }
+
+ /// @brief Get all items for the particular option space.
+ ///
+ /// @warning when there are no items for the specified option
+ /// space an empty container is created and returned. However
+ /// this container is not added to the list of option spaces.
+ ///
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to the container holding items.
+ ItemsContainerPtr getItems(const std::string& option_space) const {
+ const typename OptionSpaceMap::const_iterator& items =
+ option_space_map_.find(option_space);
+ if (items == option_space_map_.end()) {
+ return (ItemsContainerPtr(new ContainerType()));
+ }
+ return (items->second);
+ }
+
+ /// @brief Get a list of existing option spaces.
+ ///
+ /// @return a list of option spaces.
+ ///
+ /// @todo This function is likely to be removed once
+ /// we create a structore of OptionSpaces defined
+ /// through the configuration manager.
+ std::list<std::string> getOptionSpaceNames() {
+ std::list<std::string> names;
+ for (typename OptionSpaceMap::const_iterator space =
+ option_space_map_.begin();
+ space != option_space_map_.end(); ++space) {
+ names.push_back(space->first);
+ }
+ return (names);
+ }
+
+ /// @brief Remove all items from the container.
+ void clearItems() {
+ option_space_map_.clear();
+ }
+
+private:
+
+ /// A map holding container (option space name is the key).
+ typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+ OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcpsrv/pool.cc b/src/lib/dhcpsrv/pool.cc
index 9c91acb..3548762 100644
--- a/src/lib/dhcpsrv/pool.cc
+++ b/src/lib/dhcpsrv/pool.cc
@@ -34,7 +34,7 @@ Pool4::Pool4(const isc::asiolink::IOAddress& first,
const isc::asiolink::IOAddress& last)
:Pool(first, last) {
// check if specified address boundaries are sane
- if (first.getFamily() != AF_INET || last.getFamily() != AF_INET) {
+ if (!first.isV4() || !last.isV4()) {
isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
}
@@ -48,7 +48,7 @@ Pool4::Pool4(const isc::asiolink::IOAddress& prefix,
:Pool(prefix, IOAddress("0.0.0.0")) {
// check if the prefix is sane
- if (prefix.getFamily() != AF_INET) {
+ if (!prefix.isV4()) {
isc_throw(BadValue, "Invalid Pool4 address boundaries: not IPv4");
}
@@ -67,7 +67,7 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& first,
:Pool(first, last), type_(type), prefix_len_(0) {
// check if specified address boundaries are sane
- if (first.getFamily() != AF_INET6 || last.getFamily() != AF_INET6) {
+ if (!first.isV6() || !last.isV6()) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
@@ -98,7 +98,7 @@ Pool6::Pool6(Pool6Type type, const isc::asiolink::IOAddress& prefix,
type_(type), prefix_len_(prefix_len) {
// check if the prefix is sane
- if (prefix.getFamily() != AF_INET6) {
+ if (!prefix.isV6()) {
isc_throw(BadValue, "Invalid Pool6 address boundaries: not IPv6");
}
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index eb5e5e2..e8dc3e3 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -179,6 +179,13 @@ typedef boost::shared_ptr<Pool6> Pool6Ptr;
/// @brief a container for IPv6 Pools
typedef std::vector<Pool6Ptr> Pool6Collection;
+/// @brief a pointer to either IPv4 or IPv6 Pool
+typedef boost::shared_ptr<Pool> PoolPtr;
+
+/// @brief a container for either IPv4 or IPv6 Pools
+typedef std::vector<PoolPtr> PoolCollection;
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 78da250..0443a33 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -30,8 +30,8 @@ Subnet::Subnet(const isc::asiolink::IOAddress& prefix, uint8_t len,
:id_(getNextID()), prefix_(prefix), prefix_len_(len), t1_(t1),
t2_(t2), valid_(valid_lifetime),
last_allocated_(lastAddrInPrefix(prefix, len)) {
- if ( (prefix.getFamily() == AF_INET6 && len > 128) ||
- (prefix.getFamily() == AF_INET && len > 32) ) {
+ if ((prefix.isV6() && len > 128) ||
+ (prefix.isV4() && len > 32)) {
isc_throw(BadValue, "Invalid prefix length specified for subnet: " << len);
}
}
@@ -44,14 +44,45 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
}
void
-Subnet::addOption(OptionPtr& option, bool persistent /* = false */) {
+Subnet::addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space) {
+ // @todo Once the #2313 is merged we need to use the OptionSpace object to
+ // validate the option space name here. For now, let's check that the name
+ // is not empty as the empty namespace has a special meaning here - it is
+ // returned when desired namespace is not found when getOptions is called.
+ if (option_space.empty()) {
+ isc_throw(isc::BadValue, "option space name must not be empty");
+ }
validateOption(option);
- options_.push_back(OptionDescriptor(option, persistent));
+
+ // Actually add new option descriptor.
+ option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
}
void
Subnet::delOptions() {
- options_.clear();
+ option_spaces_.clearItems();
+}
+
+Subnet::OptionContainerPtr
+Subnet::getOptionDescriptors(const std::string& option_space) const {
+ return (option_spaces_.getItems(option_space));
+}
+
+Subnet::OptionDescriptor
+Subnet::getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code) {
+ OptionContainerPtr options = getOptionDescriptors(option_space);
+ if (!options || options->empty()) {
+ return (OptionDescriptor(false));
+ }
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ const OptionContainerTypeRange& range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDescriptor(false));
+ }
+
+ return (*range.first);
}
std::string Subnet::toText() const {
@@ -65,20 +96,20 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime)
:Subnet(prefix, length, t1, t2, valid_lifetime) {
- if (prefix.getFamily() != AF_INET) {
+ if (!prefix.isV4()) {
isc_throw(BadValue, "Non IPv4 prefix " << prefix.toText()
<< " specified in subnet4");
}
}
-void Subnet4::addPool4(const Pool4Ptr& pool) {
+void Subnet::addPool(const PoolPtr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet4");
+ isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+ << " does not belong in this (" << prefix_.toText() << "/"
+ << static_cast<int>(prefix_len_) << ") subnet4");
}
/// @todo: Check that pools do not overlap
@@ -86,9 +117,10 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
pools_.push_back(pool);
}
-Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool4Ptr candidate;
- for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
+
+ PoolPtr candidate;
+ for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
// if we won't find anything better, then let's just use the first pool
if (!candidate) {
@@ -104,6 +136,7 @@ Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("
return (candidate);
}
+
void
Subnet4::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -113,14 +146,14 @@ Subnet4::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
if (!inRange(addr)) {
return (false);
}
- for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
if ((*pool)->inRange(addr)) {
return (true);
}
@@ -136,45 +169,12 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
const Triplet<uint32_t>& valid_lifetime)
:Subnet(prefix, length, t1, t2, valid_lifetime),
preferred_(preferred_lifetime){
- if (prefix.getFamily() != AF_INET6) {
+ if (!prefix.isV6()) {
isc_throw(BadValue, "Non IPv6 prefix " << prefix.toText()
<< " specified in subnet6");
}
}
-void Subnet6::addPool6(const Pool6Ptr& pool) {
- IOAddress first_addr = pool->getFirstAddress();
- IOAddress last_addr = pool->getLastAddress();
-
- if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet6");
- }
-
- /// @todo: Check that pools do not overlap
-
- pools_.push_back(pool);
-}
-
-Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool6Ptr candidate;
- for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-
- // if we won't find anything better, then let's just use the first pool
- if (!candidate) {
- candidate = *pool;
- }
-
- // if the client provided a pool and there's a pool that hint is valid in,
- // then let's use that pool
- if ((*pool)->inRange(hint)) {
- return (*pool);
- }
- }
- return (candidate);
-}
-
void
Subnet6::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -184,21 +184,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
- // Let's start with checking if it even belongs to that subnet.
- if (!inRange(addr)) {
- return (false);
- }
+void Subnet6::setIface(const std::string& iface_name) {
+ iface_ = iface_name;
+}
- for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
- if ((*pool)->inRange(addr)) {
- return (true);
- }
- }
- // there's no pool that address belongs to
- return (false);
+std::string Subnet6::getIface() const {
+ return (iface_);
}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index aa1ef1f..471fb03 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/triplet.h>
@@ -78,6 +79,9 @@ public:
: option(OptionPtr()), persistent(persist) {};
};
+ /// A pointer to option descriptor.
+ typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
/// @brief Extractor class to extract key with another key.
///
/// This class solves the problem of accessing index key values
@@ -172,7 +176,7 @@ public:
// Use option type as the index key. The type is held
// in OptionPtr object so we have to call Option::getType
// to retrieve this key for each element.
- boost::multi_index::mem_fun<
+ boost::multi_index::const_mem_fun<
Option,
uint16_t,
&Option::getType
@@ -198,6 +202,8 @@ public:
>
> OptionContainer;
+ // Pointer to the OptionContainer object.
+ typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
/// Type of the index #1 - option type.
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Pair of iterators to represent the range of options having the
@@ -216,9 +222,11 @@ public:
/// @param option option instance.
/// @param persistent if true, send an option regardless if client
/// requested it or not.
+ /// @param option_space name of the option space to add an option to.
///
/// @throw isc::BadValue if invalid option provided.
- void addOption(OptionPtr& option, bool persistent = false);
+ void addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space);
/// @brief Delete all options configured for the subnet.
void delOptions();
@@ -235,7 +243,7 @@ public:
/// @param addr this address will be checked if it belongs to any pools in
/// that subnet
/// @return true if the address is in any of the pools
- virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+ bool inPool(const isc::asiolink::IOAddress& addr) const;
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
@@ -252,14 +260,24 @@ public:
return (t2_);
}
- /// @brief Return a collection of options.
+ /// @brief Return a collection of option descriptors.
///
- /// @return reference to collection of options configured for a subnet.
- /// The returned reference is valid as long as the Subnet object which
- /// returned it still exists.
- const OptionContainer& getOptions() const {
- return (options_);
- }
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to collection of options configured for a subnet.
+ OptionContainerPtr
+ getOptionDescriptors(const std::string& option_space) const;
+
+ /// @brief Return single option descriptor.
+ ///
+ /// @param option_space name of the option space.
+ /// @param option_code code of the option to be returned.
+ ///
+ /// @return option descriptor found for the specified option space
+ /// and option code.
+ OptionDescriptor
+ getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code);
/// @brief returns the last address that was tried from this pool
///
@@ -298,6 +316,37 @@ public:
return (std::make_pair(prefix_, prefix_len_));
}
+ /// @brief Adds a new pool.
+ /// @param pool pool to be added
+ void addPool(const PoolPtr& pool);
+
+ /// @brief Returns a pool that specified address belongs to
+ ///
+ /// @param addr address that the returned pool should cover (optional)
+ /// @return Pointer to found Pool4 or Pool6 (or NULL)
+ PoolPtr getPool(isc::asiolink::IOAddress addr);
+
+ /// @brief Returns a pool without any address specified
+ /// @return returns one of the pools defined
+ PoolPtr getPool() {
+ return (getPool(default_pool()));
+ }
+
+ /// @brief Returns the default address that will be used for pool selection
+ ///
+ /// It must be implemented in derived classes (should return :: for Subnet6
+ /// and 0.0.0.0 for Subnet4)
+ virtual isc::asiolink::IOAddress default_pool() const = 0;
+
+ /// @brief returns all pools
+ ///
+ /// The reference is only valid as long as the object that returned it.
+ ///
+ /// @return a collection of all pools
+ const PoolCollection& getPools() const {
+ return pools_;
+ }
+
/// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
///
/// @return textual representation
@@ -338,6 +387,9 @@ protected:
/// a Subnet4 or Subnet6.
SubnetID id_;
+ /// @brief collection of pools in that list
+ PoolCollection pools_;
+
/// @brief a prefix of the subnet
isc::asiolink::IOAddress prefix_;
@@ -353,9 +405,6 @@ protected:
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
- /// @brief a collection of DHCP options configured for a subnet.
- OptionContainer options_;
-
/// @brief last allocated address
///
/// This is the last allocated address that was previously allocated from
@@ -366,8 +415,22 @@ protected:
/// that purpose it should be only considered a help that should not be
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
+
+ /// @brief Name of the network interface (if connected directly)
+ std::string iface_;
+
+private:
+
+ /// A collection of option spaces grouping option descriptors.
+ typedef OptionSpaceContainer<OptionContainer,
+ OptionDescriptor> OptionSpaceCollection;
+ OptionSpaceCollection option_spaces_;
+
};
+/// @brief A generic pointer to either Subnet4 or Subnet6 object
+typedef boost::shared_ptr<Subnet> SubnetPtr;
+
/// @brief A configuration holder for IPv4 subnet.
///
/// This class represents an IPv4 subnet.
@@ -386,34 +449,6 @@ public:
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
- /// @brief Returns a pool that specified address belongs to
- ///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool4 (or NULL)
- Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("0.0.0.0"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool4(const Pool4Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that returned it.
- ///
- /// @return a collection of all pools
- const Pool4Collection& getPools() const {
- return pools_;
- }
-
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
-
protected:
/// @brief Check if option is valid and can be added to a subnet.
@@ -423,8 +458,11 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
- /// @brief collection of pools in that list
- Pool4Collection pools_;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv4 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("0.0.0.0"));
+ }
};
/// @brief A pointer to a Subnet4 object
@@ -461,34 +499,17 @@ public:
return (preferred_);
}
- /// @brief Returns a pool that specified address belongs to
+ /// @brief sets name of the network interface for directly attached networks
///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool6 (or NULL)
- Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("::"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool6(const Pool6Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that
- /// returned it.
- ///
- /// @return a collection of all pools
- const Pool6Collection& getPools() const {
- return pools_;
- }
+ /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
+ /// possible to decide that based on addresses assigned to network interfaces,
+ /// as DHCPv6 operates on link-local (and site local) addresses.
+ /// @param iface_name name of the interface
+ void setIface(const std::string& iface_name);
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
+ /// @brief network interface name used to reach subnet (or "" for remote subnets)
+ /// @return network interface name for directly attached subnets or ""
+ std::string getIface() const;
protected:
@@ -499,6 +520,12 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv6 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("::"));
+ }
+
/// @brief collection of pools in that list
Pool6Collection pools_;
diff --git a/src/lib/dhcpsrv/tests/.gitignore b/src/lib/dhcpsrv/tests/.gitignore
new file mode 100644
index 0000000..7add7fb
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/.gitignore
@@ -0,0 +1 @@
+/libdhcpsrv_unittests
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 8de5fda..dfe3e78 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -36,10 +36,12 @@ libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
+libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
libdhcpsrv_unittests_SOURCES += schema_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
libdhcpsrv_unittests_SOURCES += triplet_unittest.cc
+libdhcpsrv_unittests_SOURCES += test_utils.cc test_utils.h
libdhcpsrv_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
if HAVE_MYSQL
diff --git a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
index 19f08a9..cdd59df 100644
--- a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
+++ b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
@@ -16,6 +16,7 @@
#include <config.h>
#include <dhcpsrv/addr_utilities.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -28,6 +29,8 @@ using namespace std;
using namespace isc::dhcp;
using namespace isc::asiolink;
+namespace {
+
// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
IOAddress addr1("192.0.2.1");
@@ -154,3 +157,47 @@ TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
}
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+ EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+ EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+ EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+ EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+ EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+ EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+ EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+ EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+ EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+ EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+ EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+ EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+ EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+ EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+ EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+ EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+ EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+ EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+ EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+ EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+ EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+ EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+ EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+ EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+ EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+ EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+ EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+ EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+ EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+ EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+ EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+ EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+ EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+ EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index dcfb68b..078998d 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -16,41 +16,59 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <dhcpsrv/tests/test_utils.h>
+
#include <boost/shared_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <iostream>
#include <sstream>
-#include <map>
+#include <set>
+#include <time.h>
using namespace std;
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::dhcp::test;
namespace {
+/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
public:
+
+ /// @brief the sole constructor
+ /// @param engine_type specifies engine type (e.g. iterative)
+ /// @param attempts number of lease selection attempts before giving up
NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts)
:AllocEngine(engine_type, attempts) {
}
+
+ // Expose internal classes for testing purposes
using AllocEngine::Allocator;
using AllocEngine::IterativeAllocator;
};
-// empty class for now, but may be extended once Addr6 becomes bigger
-class AllocEngineTest : public ::testing::Test {
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
public:
- AllocEngineTest() {
- duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+
+ /// @brief Default constructor
+ ///
+ /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+ /// in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine6Test() {
+ duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
iaid_ = 42;
// instantiate cfg_mgr
@@ -59,12 +77,15 @@ public:
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1::10"),
IOAddress("2001:db8:1::20")));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
factory_.create("type=memfile");
}
+ /// @brief checks if Lease6 matches expected configuration
+ ///
+ /// @param lease lease to be checked
void checkLease6(const Lease6Ptr& lease) {
// that is belongs to the right subnet
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
@@ -84,20 +105,80 @@ public:
// @todo: check cltt
}
- ~AllocEngineTest() {
+ ~AllocEngine6Test() {
factory_.destroy();
}
- DuidPtr duid_;
- uint32_t iaid_;
- Subnet6Ptr subnet_;
- Pool6Ptr pool_;
- LeaseMgrFactory factory_;
+ DuidPtr duid_; ///< client-identifier (value used in tests)
+ uint32_t iaid_; ///< IA identifier (value used in tests)
+ Subnet6Ptr subnet_; ///< subnet6 (used in tests)
+ Pool6Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+ /// @brief Default constructor
+ ///
+ /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+ /// used in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine4Test() {
+ clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+ static uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+ // Let's use odd hardware type to check if there is no Ethernet
+ // hardcoded anywhere.
+ hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+ // instantiate cfg_mgr
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.109")));
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ factory_.create("type=memfile");
+ }
+
+ /// @brief checks if Lease4 matches expected configuration
+ ///
+ /// @param lease lease to be checked
+ void checkLease4(const Lease4Ptr& lease) {
+ // that is belongs to the right subnet
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(lease->addr_));
+
+ // that it have proper parameters
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+ EXPECT_EQ(subnet_->getT1(), lease->t1_);
+ EXPECT_EQ(subnet_->getT2(), lease->t2_);
+ EXPECT_TRUE(false == lease->fqdn_fwd_);
+ EXPECT_TRUE(false == lease->fqdn_rev_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
+ // @todo: check cltt
+ }
+
+ ~AllocEngine4Test() {
+ factory_.destroy();
+ }
+
+ ClientIdPtr clientid_; ///< client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< hardware address (value used in tests)
+ Subnet4Ptr subnet_; ///< subnet4 (used in tests)
+ Pool4Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
};
// This test checks if the Allocation Engine can be instantiated and that it
// parses parameters string properly.
-TEST_F(AllocEngineTest, constructor) {
+TEST_F(AllocEngine6Test, constructor) {
boost::scoped_ptr<AllocEngine> x;
// Hashed and random allocators are not supported yet
@@ -107,28 +188,8 @@ TEST_F(AllocEngineTest, constructor) {
ASSERT_NO_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
}
-/// @todo: This method is taken from mysql_lease_mgr_utilities.cc from ticket
-/// #2342. Get rid of one instance once the code is merged
-void
-detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
- EXPECT_EQ(first->type_, second->type_);
-
- // Compare address strings - odd things happen when they are different
- // as the EXPECT_EQ appears to call the operator uint32_t() function,
- // which causes an exception to be thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
- EXPECT_EQ(first->prefixlen_, second->prefixlen_);
- EXPECT_EQ(first->iaid_, second->iaid_);
- EXPECT_TRUE(*first->duid_ == *second->duid_);
- EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
- EXPECT_EQ(first->valid_lft_, second->valid_lft_);
- EXPECT_EQ(first->cltt_, second->cltt_);
- EXPECT_EQ(first->subnet_id_, second->subnet_id_);
-}
-
-
// This test checks if the simple allocation can succeed
-TEST_F(AllocEngineTest, simpleAlloc) {
+TEST_F(AllocEngine6Test, simpleAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -147,11 +208,11 @@ TEST_F(AllocEngineTest, simpleAlloc) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the fake allocation (for SOLICIT) can succeed
-TEST_F(AllocEngineTest, fakeAlloc) {
+TEST_F(AllocEngine6Test, fakeAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -172,7 +233,7 @@ TEST_F(AllocEngineTest, fakeAlloc) {
// This test checks if the allocation with a hint that is valid (in range,
// in pool and free) can succeed
-TEST_F(AllocEngineTest, allocWithValidHint) {
+TEST_F(AllocEngine6Test, allocWithValidHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -195,20 +256,21 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the allocation with a hint that is in range,
// in pool, but is currently used) can succeed
-TEST_F(AllocEngineTest, allocWithUsedHint) {
+TEST_F(AllocEngine6Test, allocWithUsedHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
// let's create a lease and put it in the LeaseMgr
DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+ time_t now = time(NULL);
Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
- duid2, 1, 2, 3, 4, 5, subnet_->getID()));
+ duid2, 1, 2, 3, 4, now, subnet_->getID()));
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
// another client comes in and request an address that is in pool, but
@@ -234,12 +296,12 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
-TEST_F(AllocEngineTest, allocBogusHint) {
+TEST_F(AllocEngine6Test, allocBogusHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -264,12 +326,12 @@ TEST_F(AllocEngineTest, allocBogusHint) {
ASSERT_TRUE(from_mgr);
// Now check that the lease in LeaseMgr has the same parameters
- detailCompareLease6(lease, from_mgr);
+ detailCompareLease(lease, from_mgr);
}
// This test verifies that the allocator picks addresses that belong to the
// pool
-TEST_F(AllocEngineTest, IterativeAllocator) {
+TEST_F(AllocEngine6Test, IterativeAllocator) {
boost::scoped_ptr<NakedAllocEngine::Allocator>
alloc(new NakedAllocEngine::IterativeAllocator());
@@ -283,7 +345,7 @@ TEST_F(AllocEngineTest, IterativeAllocator) {
// This test verifies that the iterative allocator really walks over all addresses
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
-TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
+TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
@@ -296,14 +358,14 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
IOAddress(max.str())));
// cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
- subnet_->addPool6(pool);
+ subnet_->addPool(pool);
}
int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
// there are 8 extra pools with 9 addresses in each.
// Let's keep picked addresses here and check their uniqueness.
- std::map<IOAddress, int> generated_addrs;
+ std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
@@ -316,7 +378,381 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
if (generated_addrs.find(candidate) == generated_addrs.end()) {
// we haven't had this
- generated_addrs[candidate] = 0;
+ generated_addrs.insert(candidate);
+ } else {
+ // we have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // we have exactly the number of address in all pools
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if ( cnt>total ) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+
+ delete alloc;
+}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine6Test, smallPool6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ false);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());
+
+ // do all checks on the lease
+ checkLease6(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine6Test, outOfAddresses6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ EXPECT_THROW(engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),false),
+ AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
+TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Just a different duid
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
+ // CASE 1: Asking for any address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease6(lease);
+
+ // CASE 2: Asking specifically for this address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress(addr.toText()),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("2001:db8:1::ad");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets6(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
+ pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet6(subnet_);
+
+ // Let's create an expired lease
+ DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
+ const uint32_t other_iaid = 3568;
+ const SubnetID other_subnetid = 999;
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, other_duid, other_iaid,
+ 501, 502, 503, 504, other_subnetid, 0));
+ lease->cltt_ = time(NULL) - 500; // Allocated 500 seconds ago
+ lease->valid_lft_ = 495; // Lease was valid for 495 seconds
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ lease = engine->allocateAddress6(subnet_, duid_, iaid_,
+ IOAddress(addr.toText()), false);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// --- IPv4 ---
+
+// This test checks if the simple IPv4 allocation can succeed
+TEST_F(AllocEngine4Test, simpleAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for DISCOVER) can succeed
+TEST_F(AllocEngine4Test, fakeAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is NOT in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_FALSE(from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine4Test, allocWithValidHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.105"),
+ false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should get what we asked for
+ EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used) can succeed
+TEST_F(AllocEngine4Test, allocWithUsedHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ clientid2, sizeof(clientid2), 1, 2, 3, now, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Another client comes in and request an address that is in pool, but
+ // unfortunately it is used already. The same address must not be allocated
+ // twice.
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.106"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Allocated address must be different
+ EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine4Test, allocBogusHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Client would like to get a 3000::abc lease, which does not belong to any
+ // supported lease. Allocation engine should ignore it and carry on
+ // with the normal allocation
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("10.1.1.1"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(AllocEngine4Test, IterativeAllocator) {
+ boost::scoped_ptr<NakedAllocEngine::Allocator>
+ alloc(new NakedAllocEngine::IterativeAllocator());
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_,
+ IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+ }
+}
+
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
+ NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+ // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "192.0.2." << i * 10 + 1;
+ max << "192.0.2." << i * 10 + 9;
+
+ Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+ IOAddress(max.str())));
+ // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+ subnet_->addPool(pool);
+ }
+
+ int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined subnets.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this
+ generated_addrs.insert(candidate);
} else {
// we have seen this address before. That should mean that we
// iterated over all addresses.
@@ -337,4 +773,193 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
delete alloc;
}
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine4Test, smallPool4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine4Test, outOfAddresses4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2),
+ 501, 502, 503, now, subnet_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ EXPECT_THROW(engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),false),
+ AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in DISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease4(lease);
+
+ // CASE 2: Asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.105");
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress(addr.toText()), false);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease is really renewed when renewLease4 method is
+// called
+TEST_F(AllocEngine4Test, renewLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.102");
+ const uint32_t old_lifetime = 100;
+ const uint32_t old_t1 = 50;
+ const uint32_t old_t2 = 75;
+ const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
+
+ // Just a different hw/client-id for the second client
+ const uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2,
+ sizeof(hwaddr2), old_lifetime, old_t1, old_t2,
+ old_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
+ // renew it.
+ ASSERT_FALSE(lease->expired());
+ lease = engine->renewLease4(subnet_, clientid_, hwaddr_, lease, false);
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
+ checkLease4(lease);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 7b23bcf..9b3d61b 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -39,13 +39,180 @@ namespace {
class CfgMgrTest : public ::testing::Test {
public:
CfgMgrTest() {
+ // make sure we start with a clean configuration
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().deleteSubnets6();
}
~CfgMgrTest() {
+ // clean up after the test
+ CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
}
};
+// This test verifies that multiple option definitions can be added
+// under different option spaces.
+TEST_F(CfgMgrTest, getOptionDefs) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs1 = cfg_mgr.getOptionDefs("isc");
+ ASSERT_TRUE(option_defs1);
+ ASSERT_EQ(10, option_defs1->size());
+
+ // Iterate over all option definitions and check that they have
+ // valid codes. Also, their order should be the same as they
+ // were added (codes 100-109).
+ uint16_t code = 100;
+ for (OptionDefContainer::const_iterator it = option_defs1->begin();
+ it != option_defs1->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs2 = cfg_mgr.getOptionDefs("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(10, option_defs2->size());
+
+ // Check that the option codes are valid.
+ code = 105;
+ for (OptionDefContainer::const_iterator it = option_defs2->begin();
+ it != option_defs2->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Let's make one more check that the empty set is returned when
+ // invalid option space is used.
+ OptionDefContainerPtr option_defs3 = cfg_mgr.getOptionDefs("non-existing");
+ ASSERT_TRUE(option_defs3);
+ EXPECT_TRUE(option_defs3->empty());
+}
+
+// This test verifies that single option definition is correctly
+// returned with getOptionDef function.
+TEST_F(CfgMgrTest, getOptionDef) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Try to get option definitions one by one using all codes
+ // that we expect to be there.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("isc", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that the option codes are valid.
+ for (uint16_t code = 105; code < 115; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("abcde", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-other-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that an option definition can be added to the standard
+ // (dhcp4 and dhcp6) option spaces when the option code is not
+ // reserved by the standard option.
+ OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def6, "dhcp6"));
+
+ OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def4, "dhcp4"));
+
+ // Try to query the option definition from an non-existing
+ // option space and expect NULL pointer.
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("non-existing", 56);
+ EXPECT_FALSE(def);
+
+ // Try to get the non-existing option definition from an
+ // existing option space.
+ EXPECT_FALSE(cfg_mgr.getOptionDef("isc", 56));
+
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST_F(CfgMgrTest, addOptionDefNegative) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // The option code 65 is reserved for standard options either in
+ // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
+ // option space fails.
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
+
+ // Try reserved option space names.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+ // Try empty option space name.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
+ // Try NULL option definition.
+ ASSERT_THROW(cfg_mgr.addOptionDef(OptionDefinitionPtr(), "isc"),
+ isc::dhcp::MalformedOptionDefinition);
+ // Try adding option definition twice and make sure that it
+ // fails on the second attempt.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ EXPECT_THROW(cfg_mgr.addOptionDef(def, "isc"), DuplicateOptionDefinition);
+}
// This test verifies if the configuration manager is able to hold and return
// valid leases
@@ -56,8 +223,8 @@ TEST_F(CfgMgrTest, subnet4) {
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
cfg_mgr.addSubnet4(subnet1);
@@ -74,12 +241,17 @@ TEST_F(CfgMgrTest, subnet4) {
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
// Try to find an address that does not belong to any subnet
- EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets4();
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
}
// This test verifies if the configuration manager is able to hold and return
// valid leases
-
TEST_F(CfgMgrTest, subnet6) {
CfgMgr& cfg_mgr = CfgMgr::instance();
@@ -87,8 +259,8 @@ TEST_F(CfgMgrTest, subnet6) {
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
cfg_mgr.addSubnet6(subnet1);
@@ -104,12 +276,83 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ // Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
+// This test verifies that new DHCPv4 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces4();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decode if a duplicate vendor space is allowed.
+}
+
+// This test verifies that new DHCPv6 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces6();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decide if a duplicate vendor space is allowed.
+}
+
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index 40ecba3..9924476 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -37,16 +38,136 @@ public:
}
};
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
+// This test checks that a database access string can be parsed correctly.
TEST_F(LeaseMgrFactoryTest, parse) {
- std::map<std::string, std::string> parameters = LeaseMgrFactory::parse(
- "param1=value1 param2=value2 param3=value3");
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea somethingelse= type=mysql");
- EXPECT_EQ("value1", parameters["param1"]);
- EXPECT_EQ("value2", parameters["param2"]);
- EXPECT_TRUE(parameters.find("type") == parameters.end());
+ EXPECT_EQ(5, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+ EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST_F(LeaseMgrFactoryTest, parseInvalid) {
+
+ // No tokens in the string, so we expect no parameters
+ std::string invalid = "";
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(0, parameters.size());
+
+ // With spaces, there are some tokens so we expect invalid parameter
+ // as there are no equals signs.
+ invalid = " \t ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ invalid = " noequalshere ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ // A single "=" is valid string, but is placed here as the result is
+ // expected to be nothing.
+ invalid = "=";
+ parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST_F(LeaseMgrFactoryTest, redactAccessString) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // ... and again to check that the position of the empty password in the
+ // string does not matter.
+ parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begion with.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql");
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
}
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 74a0fb4..8d8c7f8 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -22,6 +22,8 @@
#include <iostream>
#include <sstream>
+#include <time.h>
+
using namespace std;
using namespace isc;
using namespace isc::asiolink;
@@ -71,16 +73,6 @@ public:
return (Lease4Ptr());
}
- /// @brief Returns existing IPv4 lease for specific address and subnet
- /// @param addr address of the searched lease
- /// @param subnet_id ID of the subnet the lease must belong to
- ///
- /// @return smart pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const isc::asiolink::IOAddress&,
- SubnetID) const {
- return (Lease4Ptr());
- }
-
/// @brief Returns existing IPv4 leases for specified hardware address.
///
/// Although in the usual case there will be only one lease, for mobile
@@ -177,19 +169,11 @@ public:
/// @brief Deletes a lease.
///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease4(const isc::asiolink::IOAddress&) {
- return (false);
- }
-
- /// @brief Deletes a lease.
- ///
- /// @param addr IPv4 address of the lease to be deleted.
+ /// @param addr Address of the lease to be deleted. (This can be either
+ /// a V4 address or a V6 address.)
///
/// @return true if deletion was successful, false if no such lease exists
- virtual bool deleteLease6(const isc::asiolink::IOAddress&) {
+ virtual bool deleteLease(const isc::asiolink::IOAddress&) {
return (false);
}
@@ -236,16 +220,12 @@ public:
};
namespace {
-// empty class for now, but may be extended once Addr6 becomes bigger
-class LeaseMgrTest : public ::testing::Test {
-public:
- LeaseMgrTest() {
- }
-};
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
-TEST_F(LeaseMgrTest, getParameter) {
+/// @brief getParameter test
+///
+/// This test checks if the LeaseMgr can be instantiated and that it
+/// parses parameters string properly.
+TEST(LeaseMgr, getParameter) {
LeaseMgr::ParameterMap pmap;
pmap[std::string("param1")] = std::string("value1");
@@ -261,36 +241,393 @@ TEST_F(LeaseMgrTest, getParameter) {
// are purely virtual, so we would only call ConcreteLeaseMgr methods.
// Those methods are just stubs that do not return anything.
+/// @brief Lease4 Constructor Test
+///
+/// Lease4 is also defined in lease_mgr.h, so is tested in this file as well.
+// This test checks if the Lease4 structure can be instantiated correctly
+TEST(Lease4, Lease4Constructor) {
+
+ // Random values for the tests
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+
+ // ...and a time
+ const time_t current_time = time(NULL);
+
+ // Other random constants.
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // We want to check that various addresses work, so let's iterate over
+ // these.
+ const uint32_t ADDRESS[] = {
+ 0x00000000, 0x01020304, 0x7fffffff, 0x80000000, 0x80000001, 0xffffffff
+ };
+
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+
+ // Create the lease
+ Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
+ SUBNET_ID);
+
+ EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
+ EXPECT_EQ(0, lease.ext_);
+ EXPECT_TRUE(hwaddr == lease.hwaddr_);
+ EXPECT_TRUE(clientid == *lease.client_id_);
+ EXPECT_EQ(0, lease.t1_);
+ EXPECT_EQ(0, lease.t2_);
+ EXPECT_EQ(VALID_LIFETIME, lease.valid_lft_);
+ EXPECT_EQ(current_time, lease.cltt_);
+ EXPECT_EQ(SUBNET_ID, lease.subnet_id_);
+ EXPECT_FALSE(lease.fixed_);
+ EXPECT_TRUE(lease.hostname_.empty());
+ EXPECT_FALSE(lease.fqdn_fwd_);
+ EXPECT_FALSE(lease.fqdn_rev_);
+ EXPECT_TRUE(lease.comments_.empty());
+ }
+}
+
+/// @brief Lease4 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease4, OperatorEquals) {
+
+ // Random values for the tests
+ const uint32_t ADDRESS = 0x01020304;
+ const uint8_t HWADDR[] = {0x08, 0x00, 0x2b, 0x02, 0x3f, 0x4e};
+ std::vector<uint8_t> hwaddr(HWADDR, HWADDR + sizeof(HWADDR));
+ const uint8_t CLIENTID[] = {0x17, 0x34, 0xe2, 0xff, 0x09, 0x92, 0x54};
+ std::vector<uint8_t> clientid_vec(CLIENTID, CLIENTID + sizeof(CLIENTID));
+ ClientId clientid(clientid_vec);
+ const time_t current_time = time(NULL);
+ const uint32_t SUBNET_ID = 42;
+ const uint32_t VALID_LIFETIME = 500;
+
+ // Check when the leases are equal.
+ Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
+ SUBNET_ID);
+ Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
+ SUBNET_ID);
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Now vary individual fields in a lease and check that the leases compare
+ // not equal in every case.
+ lease1.addr_ = IOAddress(ADDRESS + 1);
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.ext_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.ext_ = lease2.ext_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.hwaddr_[0];
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hwaddr_ = lease2.hwaddr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --clientid_vec[0];
+ lease1.client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t1_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t1_ = lease2.t1_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t2_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t2_ = lease2.t2_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fixed_ = !lease1.fixed_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fixed_ = lease2.fixed_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.comments_ += string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.comments_ = lease2.comments_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+
+
// Lease6 is also defined in lease_mgr.h, so is tested in this file as well.
// This test checks if the Lease6 structure can be instantiated correctly
TEST(Lease6, Lease6Constructor) {
- IOAddress addr("2001:db8:1::456");
+ // check a variety of addresses with different bits set.
+ const char* ADDRESS[] = {
+ "::", "::1", "2001:db8:1::456",
+ "7fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+ "8000::", "8000::1",
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"
+ };
+ // Other values
uint8_t llt[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
DuidPtr duid(new DUID(llt, sizeof(llt)));
-
uint32_t iaid = 7; // just a number
-
SubnetID subnet_id = 8; // just another number
- Lease6Ptr x(new Lease6(Lease6::LEASE_IA_NA, addr,
- duid, iaid, 100, 200, 50, 80,
- subnet_id));
-
- EXPECT_TRUE(x->addr_ == addr);
- EXPECT_TRUE(*x->duid_ == *duid);
- EXPECT_TRUE(x->iaid_ == iaid);
- EXPECT_TRUE(x->subnet_id_ == subnet_id);
- EXPECT_TRUE(x->type_ == Lease6::LEASE_IA_NA);
- EXPECT_TRUE(x->preferred_lft_ == 100);
- EXPECT_TRUE(x->valid_lft_ == 200);
- EXPECT_TRUE(x->t1_ == 50);
- EXPECT_TRUE(x->t2_ == 80);
+ for (int i = 0; i < sizeof(ADDRESS) / sizeof(ADDRESS[0]); ++i) {
+ IOAddress addr(ADDRESS[i]);
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr,
+ duid, iaid, 100, 200, 50, 80,
+ subnet_id));
+
+ EXPECT_TRUE(lease->addr_ == addr);
+ EXPECT_TRUE(*lease->duid_ == *duid);
+ EXPECT_TRUE(lease->iaid_ == iaid);
+ EXPECT_TRUE(lease->subnet_id_ == subnet_id);
+ EXPECT_TRUE(lease->type_ == Lease6::LEASE_IA_NA);
+ EXPECT_TRUE(lease->preferred_lft_ == 100);
+ EXPECT_TRUE(lease->valid_lft_ == 200);
+ EXPECT_TRUE(lease->t1_ == 50);
+ EXPECT_TRUE(lease->t2_ == 80);
+ }
// Lease6 must be instantiated with a DUID, not with NULL pointer
+ IOAddress addr(ADDRESS[0]);
EXPECT_THROW(new Lease6(Lease6::LEASE_IA_NA, addr,
DuidPtr(), iaid, 100, 200, 50, 80,
subnet_id), InvalidOperation);
}
+
+/// @brief Lease6 Equality Test
+///
+/// Checks that the operator==() correctly compares two leases for equality.
+/// As operator!=() is also defined for this class, every check on operator==()
+/// is followed by the reverse check on operator!=().
+TEST(Lease6, OperatorEquals) {
+
+ // check a variety of addressemas with different bits set.
+ const IOAddress addr("2001:db8:1::456");
+ uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ uint32_t iaid = 7; // just a number
+ SubnetID subnet_id = 8; // just another number
+
+ // Check for equality.
+ Lease6 lease1(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+ Lease6 lease2(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+ EXPECT_TRUE(lease1 == lease2);
+ EXPECT_FALSE(lease1 != lease2);
+
+ // Go through and alter all the fields one by one
+
+ lease1.addr_ = IOAddress("::1");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.addr_ = lease2.addr_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.type_ = Lease6::LEASE_IA_PD;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.type_ = lease2.type_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.prefixlen_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.prefixlen_ = lease2.prefixlen_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.iaid_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.iaid_ = lease2.iaid_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ --duid_array[0];
+ lease1.duid_.reset(new DUID(duid_array, sizeof(duid_array)));
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.preferred_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.preferred_lft_ = lease2.preferred_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.valid_lft_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.valid_lft_ = lease2.valid_lft_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t1_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t1_ = lease2.t1_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.t2_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.t2_ = lease2.t2_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.cltt_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.cltt_ = lease2.cltt_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ ++lease1.subnet_id_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.subnet_id_ = lease2.subnet_id_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fixed_ = !lease1.fixed_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fixed_ = lease2.fixed_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.hostname_ += string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.hostname_ = lease2.hostname_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_fwd_ = !lease1.fqdn_fwd_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_fwd_ = lease2.fqdn_fwd_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.fqdn_rev_ = !lease1.fqdn_rev_;
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.fqdn_rev_ = lease2.fqdn_rev_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+
+ lease1.comments_ += string("Something random");
+ EXPECT_FALSE(lease1 == lease2);
+ EXPECT_TRUE(lease1 != lease2);
+ lease1.comments_ = lease2.comments_;
+ EXPECT_TRUE(lease1 == lease2); // Check that the reversion has made the
+ EXPECT_FALSE(lease1 != lease2); // ... leases equal
+}
+
+// Checks if lease expiration is calculated properly
+TEST(Lease6, Lease6Expired) {
+ const IOAddress addr("2001:db8:1::456");
+ const uint8_t duid_array[] = {0, 1, 2, 3, 4, 5, 6, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf};
+ const DuidPtr duid(new DUID(duid_array, sizeof(duid_array)));
+ const uint32_t iaid = 7; // just a number
+ const SubnetID subnet_id = 8; // just another number
+ Lease6 lease(Lease6::LEASE_IA_NA, addr, duid, iaid, 100, 200, 50, 80,
+ subnet_id);
+
+ // case 1: a second before expiration
+ lease.cltt_ = time(NULL) - 100;
+ lease.valid_lft_ = 101;
+ EXPECT_FALSE(lease.expired());
+
+ // case 2: the lease will expire after this second is concluded
+ lease.cltt_ = time(NULL) - 101;
+ EXPECT_FALSE(lease.expired());
+
+ // case 3: the lease is expired
+ lease.cltt_ = time(NULL) - 102;
+ EXPECT_TRUE(lease.expired());
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index 0f026f2..08186dc 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -53,7 +53,7 @@ TEST_F(MemfileLeaseMgrTest, getTypeAndName) {
boost::scoped_ptr<Memfile_LeaseMgr> lease_mgr(new Memfile_LeaseMgr(pmap));
EXPECT_EQ(std::string("memfile"), lease_mgr->getType());
- EXPECT_EQ(std::string("memfile"), lease_mgr->getName());
+ EXPECT_EQ(std::string("memory"), lease_mgr->getName());
}
// Checks that adding/getting/deleting a Lease6 object works.
@@ -120,10 +120,10 @@ TEST_F(MemfileLeaseMgrTest, addGetDelete6) {
EXPECT_FALSE(y);
// should return false - there's no such address
- EXPECT_FALSE(lease_mgr->deleteLease6(IOAddress("2001:db8:1::789")));
+ EXPECT_FALSE(lease_mgr->deleteLease(IOAddress("2001:db8:1::789")));
// this one should succeed
- EXPECT_TRUE(lease_mgr->deleteLease6(IOAddress("2001:db8:1::456")));
+ EXPECT_TRUE(lease_mgr->deleteLease(IOAddress("2001:db8:1::456")));
// after the lease is deleted, it should really be gone
x = lease_mgr->getLease6(IOAddress("2001:db8:1::456"));
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 930bc06..ddb2645 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,11 @@
#include <asiolink/io_address.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/mysql_lease_mgr.h>
+#include <dhcpsrv/tests/test_utils.h>
#include <gtest/gtest.h>
+#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
@@ -28,25 +30,29 @@
using namespace isc;
using namespace isc::asiolink;
using namespace isc::dhcp;
+using namespace isc::dhcp::test;
using namespace std;
namespace {
-// Creation of the schema
+// This holds statements to create and destroy the schema.
#include "schema_copy.h"
-// IPv6 addresseses
-const char* ADDRESS_0 = "2001:db8::0";
-const char* ADDRESS_1 = "2001:db8::1";
-const char* ADDRESS_2 = "2001:db8::2";
-const char* ADDRESS_3 = "2001:db8::3";
-const char* ADDRESS_4 = "2001:db8::4";
-const char* ADDRESS_5 = "2001:db8::5";
-const char* ADDRESS_6 = "2001:db8::6";
-const char* ADDRESS_7 = "2001:db8::7";
-
-// Connection strings. Assume:
+// IPv4 and IPv6 addresses used in the tests
+const char* ADDRESS4[] = {
+ "192.0.2.0", "192.0.2.1", "192.0.2.2", "192.0.2.3",
+ "192.0.2.4", "192.0.2.5", "192.0.2.6", "192.0.2.7",
+ NULL
+};
+const char* ADDRESS6[] = {
+ "2001:db8::0", "2001:db8::1", "2001:db8::2", "2001:db8::3",
+ "2001:db8::4", "2001:db8::5", "2001:db8::6", "2001:db8::7",
+ NULL
+};
+
+// Connection strings.
// Database: keatest
+// Host: localhost
// Username: keatest
// Password: keatest
const char* VALID_TYPE = "type=mysql";
@@ -69,7 +75,6 @@ string connectionString(const char* type, const char* name, const char* host,
if (type != NULL) {
result += string(type);
}
-
if (name != NULL) {
if (! result.empty()) {
result += space;
@@ -111,7 +116,7 @@ validConnectionString() {
// @brief Clear everything from the database
//
// There is no error checking in this code: if something fails, one of the
-// tests will fall over.
+// tests will (should) fall over.
void destroySchema() {
// Initialise
MYSQL handle;
@@ -145,7 +150,7 @@ void createSchema() {
(void) mysql_real_connect(&handle, "localhost", "keatest",
"keatest", "keatest", 0, NULL, 0);
- // Get rid of everything in it.
+ // Execute creation statements.
for (int i = 0; create_statement[i] != NULL; ++i) {
(void) mysql_query(&handle, create_statement[i]);
}
@@ -154,32 +159,37 @@ void createSchema() {
(void) mysql_close(&handle);
}
-// Note: Doxygen "///" not used - even though Doxygen is used to
-// document class and methods - to avoid the comments appearing
-// in the programming manual.
-
-// @brief Test Fixture Class
-//
-// Opens the database prior to each test and closes it afterwards.
-// All pending transactions are deleted prior to closure.
+/// @brief Test fixture class for testing MySQL Lease Manager
+///
+/// Opens the database prior to each test and closes it afterwards.
+/// All pending transactions are deleted prior to closure.
class MySqlLeaseMgrTest : public ::testing::Test {
public:
- // @brief Constructor
- //
- // Deletes everything from the database and opens it.
- MySqlLeaseMgrTest() :
- L0_ADDRESS(ADDRESS_0), L0_IOADDRESS(L0_ADDRESS),
- L1_ADDRESS(ADDRESS_1), L1_IOADDRESS(L1_ADDRESS),
- L2_ADDRESS(ADDRESS_2), L2_IOADDRESS(L2_ADDRESS),
- L3_ADDRESS(ADDRESS_3), L3_IOADDRESS(L3_ADDRESS),
- L4_ADDRESS(ADDRESS_4), L4_IOADDRESS(L4_ADDRESS),
- L5_ADDRESS(ADDRESS_5), L5_IOADDRESS(L5_ADDRESS),
- L6_ADDRESS(ADDRESS_6), L6_IOADDRESS(L6_ADDRESS),
- L7_ADDRESS(ADDRESS_7), L7_IOADDRESS(L7_ADDRESS) {
+ /// @brief Constructor
+ ///
+ /// Deletes everything from the database and opens it.
+ MySqlLeaseMgrTest() {
+ // Initialize address strings and IOAddresses
+ for (int i = 0; ADDRESS4[i] != NULL; ++i) {
+ string addr(ADDRESS4[i]);
+ straddress4_.push_back(addr);
+ IOAddress ioaddr(addr);
+ ioaddress4_.push_back(ioaddr);
+ }
+
+ for (int i = 0; ADDRESS6[i] != NULL; ++i) {
+ string addr(ADDRESS6[i]);
+ straddress6_.push_back(addr);
+ IOAddress ioaddr(addr);
+ ioaddress6_.push_back(ioaddr);
+ }
+ // Ensure schema is the correct one.
destroySchema();
createSchema();
+
+ // Connect to the database
try {
LeaseMgrFactory::create(validConnectionString());
} catch (...) {
@@ -193,38 +203,146 @@ public:
lmptr_ = &(LeaseMgrFactory::instance());
}
- // @brief Destructor
- //
- // Rolls back all pending transactions. The deletion of the
- // lmptr_ member variable will close the database. Then
- // reopen it and delete everything created by the test.
+ /// @brief Destructor
+ ///
+ /// Rolls back all pending transactions. The deletion of lmptr_ will close
+ /// the database. Then reopen it and delete everything created by the test.
virtual ~MySqlLeaseMgrTest() {
lmptr_->rollback();
LeaseMgrFactory::destroy();
destroySchema();
}
- // @brief Reopen the database
- //
- // Closes the database and re-open it. Anything committed should be
- // visible.
+ /// @brief Reopen the database
+ ///
+ /// Closes the database and re-open it. Anything committed should be
+ /// visible.
void reopen() {
LeaseMgrFactory::destroy();
LeaseMgrFactory::create(validConnectionString());
lmptr_ = &(LeaseMgrFactory::instance());
}
- // @brief Initialize Lease6 Fields
- //
- // Returns a pointer to a Lease6 structure. Different values are put
- // in the lease according to the address passed.
- //
- // This is just a convenience function for the test methods.
- //
- // @param address Address to use for the initialization
- //
- // @return Lease6Ptr. This will not point to anything if the initialization
- // failed (e.g. unknown address).
+ /// @brief Initialize Lease4 Fields
+ ///
+ /// Returns a pointer to a Lease4 structure. Different values are put into
+ /// the lease according to the address passed.
+ ///
+ /// This is just a convenience function for the test methods.
+ ///
+ /// @param address Address to use for the initialization
+ ///
+ /// @return Lease4Ptr. This will not point to anything if the
+ /// initialization failed (e.g. unknown address).
+ Lease4Ptr initializeLease4(std::string address) {
+ Lease4Ptr lease(new Lease4());
+
+ // Set the address of the lease
+ lease->addr_ = IOAddress(address);
+
+ // Initialize unused fields.
+ lease->ext_ = 0; // Not saved
+ lease->t1_ = 0; // Not saved
+ lease->t2_ = 0; // Not saved
+ lease->fixed_ = false; // Unused
+ lease->hostname_ = std::string(""); // Unused
+ lease->fqdn_fwd_ = false; // Unused
+ lease->fqdn_rev_ = false; // Unused
+ lease->comments_ = std::string(""); // Unused
+
+ // Set other parameters. For historical reasons, address 0 is not used.
+ if (address == straddress4_[0]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x08);
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x42)));
+ lease->valid_lft_ = 8677;
+ lease->cltt_ = 168256;
+ lease->subnet_id_ = 23;
+
+ } else if (address == straddress4_[1]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x19);
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53)));
+ lease->valid_lft_ = 3677;
+ lease->cltt_ = 123456;
+ lease->subnet_id_ = 73;
+
+ } else if (address == straddress4_[2]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x2a);
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x64)));
+ lease->valid_lft_ = 5412;
+ lease->cltt_ = 234567;
+ lease->subnet_id_ = 73; // Same as lease 1
+
+ } else if (address == straddress4_[3]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x19); // Same as lease 1
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x75)));
+
+ // The times used in the next tests are deliberately restricted - we
+ // should be able to cope with valid lifetimes up to 0xffffffff.
+ // However, this will lead to overflows.
+ // @TODO: test overflow conditions when code has been fixed
+ lease->valid_lft_ = 7000;
+ lease->cltt_ = 234567;
+ lease->subnet_id_ = 37;
+
+ } else if (address == straddress4_[4]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x4c);
+ // Same ClientId as straddr4_[1]
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 7736;
+ lease->cltt_ = 222456;
+ lease->subnet_id_ = 85;
+
+ } else if (address == straddress4_[5]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x19); // Same as lease 1
+ // Same ClientId and IAID as straddress4_1
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 7832;
+ lease->cltt_ = 227476;
+ lease->subnet_id_ = 175;
+
+ } else if (address == straddress4_[6]) {
+ lease->hwaddr_ = vector<uint8_t>(6, 0x6e);
+ // Same ClientId as straddress4_1
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
+ lease->valid_lft_ = 1832;
+ lease->cltt_ = 627476;
+ lease->subnet_id_ = 112;
+
+ } else if (address == straddress4_[7]) {
+ lease->hwaddr_ = vector<uint8_t>(); // Empty
+ lease->client_id_ = ClientIdPtr(
+ new ClientId(vector<uint8_t>())); // Empty
+ lease->valid_lft_ = 7975;
+ lease->cltt_ = 213876;
+ lease->subnet_id_ = 19;
+
+ } else {
+ // Unknown address, return an empty pointer.
+ lease.reset();
+
+ }
+
+ return (lease);
+ }
+
+ /// @brief Initialize Lease6 Fields
+ ///
+ /// Returns a pointer to a Lease6 structure. Different values are put into
+ /// the lease according to the address passed.
+ ///
+ /// This is just a convenience function for the test methods.
+ ///
+ /// @param address Address to use for the initialization
+ ///
+ /// @return Lease6Ptr. This will not point to anything if the initialization
+ /// failed (e.g. unknown address).
Lease6Ptr initializeLease6(std::string address) {
Lease6Ptr lease(new Lease6());
@@ -240,38 +358,38 @@ public:
lease->fqdn_rev_ = false; // Unused
lease->comments_ = std::string(""); // Unused
- // Set the other parameters. For historical reasons, L0_ADDRESS is not used.
- if (address == L0_ADDRESS) {
+ // Set other parameters. For historical reasons, address 0 is not used.
+ if (address == straddress6_[0]) {
lease->type_ = Lease6::LEASE_IA_TA;
lease->prefixlen_ = 4;
lease->iaid_ = 142;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x77)));
- lease->preferred_lft_ = 900; // Preferred lifetime
- lease->valid_lft_ = 8677; // Actual lifetime
- lease->cltt_ = 168256; // Current time of day
- lease->subnet_id_ = 23; // Arbitrary number
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x77)));
+ lease->preferred_lft_ = 900;
+ lease->valid_lft_ = 8677;
+ lease->cltt_ = 168256;
+ lease->subnet_id_ = 23;
- } else if (address == L1_ADDRESS) {
+ } else if (address == straddress6_[1]) {
lease->type_ = Lease6::LEASE_IA_TA;
lease->prefixlen_ = 0;
lease->iaid_ = 42;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
- lease->preferred_lft_ = 3600; // Preferred lifetime
- lease->valid_lft_ = 3677; // Actual lifetime
- lease->cltt_ = 123456; // Current time of day
- lease->subnet_id_ = 73; // Arbitrary number
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ lease->preferred_lft_ = 3600;
+ lease->valid_lft_ = 3677;
+ lease->cltt_ = 123456;
+ lease->subnet_id_ = 73;
- } else if (address == L2_ADDRESS) {
+ } else if (address == straddress6_[2]) {
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 7;
lease->iaid_ = 89;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x3a)));
- lease->preferred_lft_ = 1800; // Preferred lifetime
- lease->valid_lft_ = 5412; // Actual lifetime
- lease->cltt_ = 234567; // Current time of day
- lease->subnet_id_ = 73; // Same as for L1_ADDRESS
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x3a)));
+ lease->preferred_lft_ = 1800;
+ lease->valid_lft_ = 5412;
+ lease->cltt_ = 234567;
+ lease->subnet_id_ = 73; // Same as lease 1
- } else if (address == L3_ADDRESS) {
+ } else if (address == straddress6_[3]) {
lease->type_ = Lease6::LEASE_IA_NA;
lease->prefixlen_ = 28;
lease->iaid_ = 0xfffffffe;
@@ -279,60 +397,62 @@ public:
for (uint8_t i = 31; i < 126; ++i) {
duid.push_back(i);
}
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(duid));
+ lease->duid_ = DuidPtr(new DUID(duid));
// The times used in the next tests are deliberately restricted - we
// should be able to cope with valid lifetimes up to 0xffffffff.
// However, this will lead to overflows.
// @TODO: test overflow conditions when code has been fixed
- lease->preferred_lft_ = 7200; // Preferred lifetime
- lease->valid_lft_ = 7000; // Actual lifetime
- lease->cltt_ = 234567; // Current time of day
- lease->subnet_id_ = 37; // Different from L1 and L2
+ lease->preferred_lft_ = 7200;
+ lease->valid_lft_ = 7000;
+ lease->cltt_ = 234567;
+ lease->subnet_id_ = 37;
- } else if (address == L4_ADDRESS) {
- // Same DUID and IAID as L1_ADDRESS
+ } else if (address == straddress6_[4]) {
+ // Same DUID and IAID as straddress6_1
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 15;
lease->iaid_ = 42;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
- lease->preferred_lft_ = 4800; // Preferred lifetime
- lease->valid_lft_ = 7736; // Actual lifetime
- lease->cltt_ = 222456; // Current time of day
- lease->subnet_id_ = 75; // Arbitrary number
-
- } else if (address == L5_ADDRESS) {
- // Same DUID and IAID as L1_ADDRESS
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ lease->preferred_lft_ = 4800;
+ lease->valid_lft_ = 7736;
+ lease->cltt_ = 222456;
+ lease->subnet_id_ = 671;
+
+ } else if (address == straddress6_[5]) {
+ // Same DUID and IAID as straddress6_1
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 24;
- lease->iaid_ = 42;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
- lease->preferred_lft_ = 5400; // Preferred lifetime
- lease->valid_lft_ = 7832; // Actual lifetime
- lease->cltt_ = 227476; // Current time of day
- lease->subnet_id_ = 175; // Arbitrary number
-
- } else if (address == L6_ADDRESS) {
- // Same DUID as L1_ADDRESS
+ lease->iaid_ = 42; // Same as lease 4
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ // Same as lease 4
+ lease->preferred_lft_ = 5400;
+ lease->valid_lft_ = 7832;
+ lease->cltt_ = 227476;
+ lease->subnet_id_ = 175;
+
+ } else if (address == straddress6_[6]) {
+ // Same DUID as straddress6_1
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 24;
lease->iaid_ = 93;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
- lease->preferred_lft_ = 5400; // Preferred lifetime
- lease->valid_lft_ = 1832; // Actual lifetime
- lease->cltt_ = 627476; // Current time of day
- lease->subnet_id_ = 112; // Arbitrary number
-
- } else if (address == L7_ADDRESS) {
- // Same IAID as L1_ADDRESS
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
+ // Same as lease 4
+ lease->preferred_lft_ = 5400;
+ lease->valid_lft_ = 1832;
+ lease->cltt_ = 627476;
+ lease->subnet_id_ = 112;
+
+ } else if (address == straddress6_[7]) {
+ // Same IAID as straddress6_1
lease->type_ = Lease6::LEASE_IA_PD;
lease->prefixlen_ = 24;
lease->iaid_ = 42;
- lease->duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xe5)));
- lease->preferred_lft_ = 5600; // Preferred lifetime
- lease->valid_lft_ = 7975; // Actual lifetime
- lease->cltt_ = 213876; // Current time of day
- lease->subnet_id_ = 19; // Arbitrary number
+ lease->duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0xe5)));
+ lease->preferred_lft_ = 5600;
+ lease->valid_lft_ = 7975;
+ lease->cltt_ = 213876;
+ lease->subnet_id_ = 19;
} else {
// Unknown address, return an empty pointer.
@@ -343,78 +463,88 @@ public:
return (lease);
}
- // @brief Creates Leases for the test
- //
- // Creates all leases for the test and checks that they are different.
- //
- // @return vector<Lease6Ptr> Vector of pointers to leases
- vector<Lease6Ptr> createLeases6() {
-
- // Create leases
- vector<Lease6Ptr> leases;
- leases.push_back(initializeLease6(L0_ADDRESS));
- leases.push_back(initializeLease6(L1_ADDRESS));
- leases.push_back(initializeLease6(L2_ADDRESS));
- leases.push_back(initializeLease6(L3_ADDRESS));
- leases.push_back(initializeLease6(L4_ADDRESS));
- leases.push_back(initializeLease6(L5_ADDRESS));
- leases.push_back(initializeLease6(L6_ADDRESS));
- leases.push_back(initializeLease6(L7_ADDRESS));
-
- EXPECT_EQ(8, leases.size());
+ /// @brief Check Leases present and different
+ ///
+ /// Checks a vector of lease pointers and ensures that all the leases
+ /// they point to are present and different. If not, a GTest assertion
+ /// will fail.
+ ///
+ /// @param leases Vector of pointers to leases
+ template <typename T>
+ void checkLeasesDifferent(const std::vector<T>& leases) const {
// Check they were created
for (int i = 0; i < leases.size(); ++i) {
- EXPECT_TRUE(leases[i]);
+ ASSERT_TRUE(leases[i]);
}
// Check they are different
for (int i = 0; i < (leases.size() - 1); ++i) {
for (int j = (i + 1); j < leases.size(); ++j) {
- EXPECT_TRUE(leases[i] != leases[j]);
+ stringstream s;
+ s << "Comparing leases " << i << " & " << j << " for equality";
+ SCOPED_TRACE(s.str());
+ EXPECT_TRUE(*leases[i] != *leases[j]);
}
}
-
- return (leases);
}
+ /// @brief Creates leases for the test
+ ///
+ /// Creates all leases for the test and checks that they are different.
+ ///
+ /// @return vector<Lease4Ptr> Vector of pointers to leases
+ vector<Lease4Ptr> createLeases4() {
+
+ // Create leases for each address
+ vector<Lease4Ptr> leases;
+ for (int i = 0; i < straddress4_.size(); ++i) {
+ leases.push_back(initializeLease4(straddress4_[i]));
+ }
+ EXPECT_EQ(8, leases.size());
- // Member variables
-
- LeaseMgr* lmptr_; // Pointer to the lease manager
+ // Check all were created and that they are different.
+ checkLeasesDifferent(leases);
- string L0_ADDRESS; // String form of address 1
- IOAddress L0_IOADDRESS; // IOAddress form of L1_ADDRESS
+ return (leases);
+ }
- string L1_ADDRESS; // String form of address 1
- IOAddress L1_IOADDRESS; // IOAddress form of L1_ADDRESS
+ /// @brief Creates leases for the test
+ ///
+ /// Creates all leases for the test and checks that they are different.
+ ///
+ /// @return vector<Lease6Ptr> Vector of pointers to leases
+ vector<Lease6Ptr> createLeases6() {
- string L2_ADDRESS; // String form of address 2
- IOAddress L2_IOADDRESS; // IOAddress form of L2_ADDRESS
+ // Create leases for each address
+ vector<Lease6Ptr> leases;
+ for (int i = 0; i < straddress6_.size(); ++i) {
+ leases.push_back(initializeLease6(straddress6_[i]));
+ }
+ EXPECT_EQ(8, leases.size());
- string L3_ADDRESS; // String form of address 3
- IOAddress L3_IOADDRESS; // IOAddress form of L3_ADDRESS
+ // Check all were created and that they are different.
+ checkLeasesDifferent(leases);
- string L4_ADDRESS; // String form of address 4
- IOAddress L4_IOADDRESS; // IOAddress form of L4_ADDRESS
+ return (leases);
+ }
- string L5_ADDRESS; // String form of address 5
- IOAddress L5_IOADDRESS; // IOAddress form of L5_ADDRESS
- string L6_ADDRESS; // String form of address 6
- IOAddress L6_IOADDRESS; // IOAddress form of L6_ADDRESS
+ // Member variables
- string L7_ADDRESS; // String form of address 7
- IOAddress L7_IOADDRESS; // IOAddress form of L7_ADDRESS
+ LeaseMgr* lmptr_; ///< Pointer to the lease manager
+ vector<string> straddress4_; ///< String forms of IPv4 addresses
+ vector<IOAddress> ioaddress4_; ///< IOAddress forms of IPv4 addresses
+ vector<string> straddress6_; ///< String forms of IPv6 addresses
+ vector<IOAddress> ioaddress6_; ///< IOAddress forms of IPv6 addresses
};
-
-// @brief Check that Database Can Be Opened
-//
-// This test checks if the MySqlLeaseMgr can be instantiated. This happens
-// only if the database can be opened. Note that this is not part of the
-// MySqlLeaseMgr test fixure set. This test checks that the database can be
-// opened: the fixtures assume that and check basic operations.
+/// @brief Check that database can be opened
+///
+/// This test checks if the MySqlLeaseMgr can be instantiated. This happens
+/// only if the database can be opened. Note that this is not part of the
+/// MySqlLeaseMgr test fixure set. This test checks that the database can be
+/// opened: the fixtures assume that and check basic operations.
TEST(MySqlOpenTest, OpenDatabase) {
@@ -472,25 +602,25 @@ TEST(MySqlOpenTest, OpenDatabase) {
destroySchema();
}
-// @brief Check the getType() method
-//
-// getType() returns a string giving the type of the backend, which should
-// always be "mysql".
+/// @brief Check the getType() method
+///
+/// getType() returns a string giving the type of the backend, which should
+/// always be "mysql".
TEST_F(MySqlLeaseMgrTest, getType) {
EXPECT_EQ(std::string("mysql"), lmptr_->getType());
}
-// @brief Check conversion functions
-//
-// The server works using cltt and valid_filetime. In the database, the
-// information is stored as expire_time and valid-lifetime, which are
-// related by
-//
-// expire_time = cltt + valid_lifetime
-//
-// This test checks that the conversion is correct. It does not check that the
-// data is entered into the database correctly, only that the MYSQL_TIME
-// structure used for the entry is correctly set up.
+/// @brief Check conversion functions
+///
+/// The server works using cltt and valid_filetime. In the database, the
+/// information is stored as expire_time and valid-lifetime, which are
+/// related by
+///
+/// expire_time = cltt + valid_lifetime
+///
+/// This test checks that the conversion is correct. It does not check that the
+/// data is entered into the database correctly, only that the MYSQL_TIME
+/// structure used for the entry is correctly set up.
TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
const time_t cltt = time(NULL);
const uint32_t valid_lft = 86400; // 1 day
@@ -522,14 +652,12 @@ TEST_F(MySqlLeaseMgrTest, checkTimeConversion) {
}
-// @brief Check getName() returns correct database name
+/// @brief Check getName() returns correct database name
TEST_F(MySqlLeaseMgrTest, getName) {
EXPECT_EQ(std::string("keatest"), lmptr_->getName());
-
- // @TODO: check for the negative
}
-// @brief Check that getVersion() returns the expected version
+/// @brief Check that getVersion() returns the expected version
TEST_F(MySqlLeaseMgrTest, checkVersion) {
// Check version
pair<uint32_t, uint32_t> version;
@@ -538,34 +666,56 @@ TEST_F(MySqlLeaseMgrTest, checkVersion) {
EXPECT_EQ(CURRENT_VERSION_MINOR, version.second);
}
-// @brief Compare two Lease6 structures for equality
-void
-detailCompareLease6(const Lease6Ptr& first, const Lease6Ptr& second) {
- EXPECT_EQ(first->type_, second->type_);
-
- // Compare address strings. Comparison of address objects is not used, as
- // odd things happen when they are different: the EXPECT_EQ macro appears to
- // call the operator uint32_t() function, which causes an exception to be
- // thrown for IPv6 addresses.
- EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
- EXPECT_EQ(first->prefixlen_, second->prefixlen_);
- EXPECT_EQ(first->iaid_, second->iaid_);
- EXPECT_TRUE(*first->duid_ == *second->duid_);
- EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
- EXPECT_EQ(first->valid_lft_, second->valid_lft_);
- EXPECT_EQ(first->cltt_, second->cltt_);
- EXPECT_EQ(first->subnet_id_, second->subnet_id_);
-}
+/// @brief Basic Lease4 Checks
+///
+/// Checks that the addLease, getLease4 (by address) and deleteLease (with an
+/// IPv4 address) works.
+TEST_F(MySqlLeaseMgrTest, basicLease4) {
+ // Get the leases to be used for the test.
+ vector<Lease4Ptr> leases = createLeases4();
+ // Start the tests. Add three leases to the database, read them back and
+ // check they are what we think they are.
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ EXPECT_TRUE(lmptr_->addLease(leases[2]));
+ EXPECT_TRUE(lmptr_->addLease(leases[3]));
+ lmptr_->commit();
-// @brief Check individual Lease6 methods
-//
-// Checks that the add/update/delete works. All are done within one
-// test so that "rollback" can be used to remove trace of the tests
-// from the database.
-//
-// Tests where a collection of leases can be returned are in the test
-// Lease6Collection.
+ // Reopen the database to ensure that they actually got stored.
+ reopen();
+
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+
+ l_returned = lmptr_->getLease4(ioaddress4_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
+
+ // Check that we can't add a second lease with the same address
+ EXPECT_FALSE(lmptr_->addLease(leases[1]));
+
+ // Delete a lease, check that it's gone, and that we can't delete it
+ // a second time.
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ EXPECT_FALSE(l_returned);
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
+
+ // Check that the second address is still there.
+ l_returned = lmptr_->getLease4(ioaddress4_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+}
+
+/// @brief Basic Lease6 Checks
+///
+/// Checks that the addLease, getLease6 (by address) and deleteLease (with an
+/// IPv6 address) works.
TEST_F(MySqlLeaseMgrTest, basicLease6) {
// Get the leases to be used for the test.
vector<Lease6Ptr> leases = createLeases6();
@@ -580,42 +730,334 @@ TEST_F(MySqlLeaseMgrTest, basicLease6) {
// Reopen the database to ensure that they actually got stored.
reopen();
- Lease6Ptr l_returned = lmptr_->getLease6(L1_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[1], l_returned);
+ Lease6Ptr l_returned = lmptr_->getLease6(ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
- l_returned = lmptr_->getLease6(L2_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[2], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
- l_returned = lmptr_->getLease6(L3_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[3], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[3]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[3], l_returned);
// Check that we can't add a second lease with the same address
EXPECT_FALSE(lmptr_->addLease(leases[1]));
// Delete a lease, check that it's gone, and that we can't delete it
// a second time.
- EXPECT_TRUE(lmptr_->deleteLease6(L1_IOADDRESS));
- l_returned = lmptr_->getLease6(L1_IOADDRESS);
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
+ l_returned = lmptr_->getLease6(ioaddress6_[1]);
EXPECT_FALSE(l_returned);
- EXPECT_FALSE(lmptr_->deleteLease6(L1_IOADDRESS));
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress6_[1]));
// Check that the second address is still there.
- l_returned = lmptr_->getLease6(L2_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[2], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[2]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[2], l_returned);
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the hardware address of lease 1
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
+
+ // Should be three leases, matching leases[1], [3] and [5].
+ ASSERT_EQ(3, returned.size());
+
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ addresses.push_back((*i)->addr_.toText());
+ }
+ sort(addresses.begin(), addresses.end());
+ EXPECT_EQ(straddress4_[1], addresses[0]);
+ EXPECT_EQ(straddress4_[3], addresses[1]);
+ EXPECT_EQ(straddress4_[5], addresses[2]);
+
+ // Repeat test with just one expected match
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
+ EXPECT_EQ(1, returned.size());
+ detailCompareLease(leases[2], *returned.begin());
+
+ // Check that an empty vector is valid
+ EXPECT_TRUE(leases[7]->hwaddr_.empty());
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
+ EXPECT_EQ(1, returned.size());
+ detailCompareLease(leases[7], *returned.begin());
+
+ // Try to get something with invalid hardware address
+ vector<uint8_t> invalid(6, 0);
+ returned = lmptr_->getLease4(invalid);
+ EXPECT_EQ(0, returned.size());
}
-// @brief Check GetLease6 methods - Access by DUID/IAID
+// @brief Get lease4 by hardware address (2)
//
-// Adds leases to the database and checks that they can be accessed via
-// a combination of DIUID and IAID.
-TEST_F(MySqlLeaseMgrTest, getLease6Extended1) {
+// Check that the system can cope with getting a hardware address of
+// any size.
+TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
+
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing hardware address size.
+ for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ leases[1]->hwaddr_.resize(i, i);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+ ASSERT_EQ(1, returned.size());
+ detailCompareLease(leases[1], *returned.begin());
+ (void) lmptr_->deleteLease(leases[1]->addr_);
+ }
+
+ // Expect some problem when accessing a lease that had too long a hardware
+ // address. (The 42 is a random value put in each byte of the address.)
+ // In fact the address is stored in a truncated form, so we won't find it
+ // when we look.
+ // @todo Check if there is some way of detecting that data added
+ // to the database is truncated. There does not appear to
+ // be any indication in the C API.
+ leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
+ EXPECT_EQ(0, returned.size());
+}
+
+/// @brief Check GetLease4 methods - access by Hardware Address & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of hardware address and subnet ID
+TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the hardware address of lease 1 and
+ // subnet ID of lease 1. Result should be a single lease - lease 1.
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+
+ // Try for a match to the hardware address of lease 1 and the wrong
+ // subnet ID.
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to the subnet ID of lease 1 (and lease 4) but
+ // the wrong hardware address.
+ vector<uint8_t> invalid_hwaddr(15, 0x77);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+ leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to an unknown hardware address and an unknown
+ // subnet ID.
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
+ leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Add a second lease with the same values as the first and check that
+ // an attempt to access the database by these parameters throws a
+ // "multiple records" exception. (We expect there to be only one record
+ // with that combination, so getting them via getLeaseX() (as opposed
+ // to getLeaseXCollection() should throw an exception.)
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
+ leases[1]->addr_ = leases[2]->addr_;
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ leases[1]->subnet_id_),
+ isc::dhcp::MultipleRecords);
+
+ // Delete all leases in the database
+ for (int i = 0; ADDRESS4[i] != NULL; ++i) {
+ IOAddress addr(ADDRESS4[i]);
+ (void) lmptr_->deleteLease(addr);
+ }
+}
+
+// @brief Get lease4 by hardware address and subnet ID (2)
+//
+// Check that the system can cope with getting a hardware address of
+// any size.
+TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
+
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing hardware address size and check
+ // that they can be retrieved.
+ for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
+ leases[1]->hwaddr_.resize(i, i);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
+ }
+
+ // Expect some error when getting a lease with too long a hardware
+ // address. Set the contents of each byte to 42, a random value.
+ // @todo Check if there is some way of detecting that data added
+ // to the database is truncated. There does not appear to
+ // be any indication in the C API.
+ leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
+ leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+}
+
+/// @brief Check GetLease4 methods - access by Client ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// the Client ID.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientId) {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the Client ID address of lease 1
+ Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+
+ // Should be four leases, matching leases[1], [4], [5] and [6].
+ ASSERT_EQ(4, returned.size());
+
+ // Easiest way to check is to look at the addresses.
+ vector<string> addresses;
+ for (Lease4Collection::const_iterator i = returned.begin();
+ i != returned.end(); ++i) {
+ addresses.push_back((*i)->addr_.toText());
+ }
+ sort(addresses.begin(), addresses.end());
+ EXPECT_EQ(straddress4_[1], addresses[0]);
+ EXPECT_EQ(straddress4_[4], addresses[1]);
+ EXPECT_EQ(straddress4_[5], addresses[2]);
+ EXPECT_EQ(straddress4_[6], addresses[3]);
+
+ // Repeat test with just one expected match
+ returned = lmptr_->getLease4(*leases[3]->client_id_);
+ EXPECT_EQ(1, returned.size());
+ detailCompareLease(leases[3], *returned.begin());
+
+ // Check that an empty vector is valid
+ EXPECT_TRUE(leases[7]->client_id_->getClientId().empty());
+ returned = lmptr_->getLease4(leases[7]->hwaddr_);
+ EXPECT_EQ(1, returned.size());
+ detailCompareLease(leases[7], *returned.begin());
+
+ // Try to get something with invalid client ID
+ const uint8_t invalid_data[] = {0, 0, 0};
+ ClientId invalid(invalid_data, sizeof(invalid_data));
+ returned = lmptr_->getLease4(invalid);
+ EXPECT_EQ(0, returned.size());
+}
+
+// @brief Get Lease4 by client ID (2)
+//
+// Check that the system can cope with a client ID of any size.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
+
+ // Create leases, although we need only one.
+ vector<Lease4Ptr> leases = createLeases4();
+
+ // Now add leases with increasing Client ID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ // Intermediate client_id_max is to overcome problem if
+ // ClientId::MAX_CLIENT_ID_LEN is used in an EXPECT_EQ.
+ int client_id_max = ClientId::MAX_CLIENT_ID_LEN;
+ EXPECT_EQ(128, client_id_max);
+ for (uint8_t i = 0; i <= client_id_max; i += 16) {
+ vector<uint8_t> clientid_vec(i, i);
+ leases[1]->client_id_.reset(new ClientId(clientid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
+ ASSERT_TRUE(returned.size() == 1);
+ detailCompareLease(leases[1], *returned.begin());
+ (void) lmptr_->deleteLease(leases[1]->addr_);
+ }
+
+ // Don't bother to check client IDs longer than the maximum -
+ // these cannot be constructed, and that limitation is tested
+ // in the DUID/Client ID unit tests.
+}
+
+/// @brief Check GetLease4 methods - access by Client ID & Subnet ID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of client and subnet IDs.
+TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSubnetId) {
+ // Get the leases to be used for the test and add to the database
+ vector<Lease4Ptr> leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Get the leases matching the client ID of lease 1 and
+ // subnet ID of lease 1. Result should be a single lease - lease 1.
+ Lease4Ptr returned = lmptr_->getLease4(*leases[1]->client_id_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+
+ // Try for a match to the client ID of lease 1 and the wrong
+ // subnet ID.
+ returned = lmptr_->getLease4(*leases[1]->client_id_,
+ leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to the subnet ID of lease 1 (and lease 4) but
+ // the wrong client ID
+ const uint8_t invalid_data[] = {0, 0, 0};
+ ClientId invalid(invalid_data, sizeof(invalid_data));
+ returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_);
+ EXPECT_FALSE(returned);
+
+ // Try for a match to an unknown hardware address and an unknown
+ // subnet ID.
+ returned = lmptr_->getLease4(invalid, leases[1]->subnet_id_ + 1);
+ EXPECT_FALSE(returned);
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaid) {
// Get the leases to be used for the test.
vector<Lease6Ptr> leases = createLeases6();
- EXPECT_LE(6, leases.size()); // Expect to access leases 0 through 5
+ ASSERT_LE(6, leases.size()); // Expect to access leases 0 through 5
// Add them to the database
for (int i = 0; i < leases.size(); ++i) {
@@ -636,9 +1078,9 @@ TEST_F(MySqlLeaseMgrTest, getLease6Extended1) {
addresses.push_back((*i)->addr_.toText());
}
sort(addresses.begin(), addresses.end());
- EXPECT_EQ(L1_ADDRESS, addresses[0]);
- EXPECT_EQ(L4_ADDRESS, addresses[1]);
- EXPECT_EQ(L5_ADDRESS, addresses[2]);
+ EXPECT_EQ(straddress6_[1], addresses[0]);
+ EXPECT_EQ(straddress6_[4], addresses[1]);
+ EXPECT_EQ(straddress6_[5], addresses[2]);
// Check that nothing is returned when either the IAID or DUID match
// nothing.
@@ -653,18 +1095,41 @@ TEST_F(MySqlLeaseMgrTest, getLease6Extended1) {
EXPECT_EQ(0, returned.size());
}
-
-
-// @brief Check GetLease6 methods - Access by DUID/IAID/SubnetID
+// @brief Get Lease4 by DUID and IAID (2)
//
-// Adds leases to the database and checks that they can be accessed via
-// a combination of DIUID and IAID.
-TEST_F(MySqlLeaseMgrTest, getLease6Extended2) {
- // Get the leases to be used for the test.
+// Check that the system can cope with a DUID of any size.
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSize) {
+
+ // Create leases, although we need only one.
vector<Lease6Ptr> leases = createLeases6();
- EXPECT_LE(6, leases.size()); // Expect to access leases 0 through 5
- // Add them to the database
+ // Now add leases with increasing DUID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ int duid_max = DUID::MAX_DUID_LEN;
+ EXPECT_EQ(128, duid_max);
+ for (uint8_t i = 0; i <= duid_max; i += 16) {
+ vector<uint8_t> duid_vec(i, i);
+ leases[1]->duid_.reset(new DUID(duid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Collection returned = lmptr_->getLease6(*leases[1]->duid_,
+ leases[1]->iaid_);
+ EXPECT_EQ(1, returned.size());
+ detailCompareLease(leases[1], *returned.begin());
+ (void) lmptr_->deleteLease(leases[1]->addr_);
+ }
+
+ // Don't bother to check DUIDs longer than the maximum - these cannot be
+ // constructed, and that limitation is tested in the DUID/Client ID unit
+ // tests.
+}
+
+/// @brief Check GetLease6 methods - access by DUID/IAID/SubnetID
+///
+/// Adds leases to the database and checks that they can be accessed via
+/// a combination of DIUID and IAID.
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetId) {
+ // Get the leases to be used for the test and add them to the database.
+ vector<Lease6Ptr> leases = createLeases6();
for (int i = 0; i < leases.size(); ++i) {
EXPECT_TRUE(lmptr_->addLease(leases[i]));
}
@@ -695,24 +1160,94 @@ TEST_F(MySqlLeaseMgrTest, getLease6Extended2) {
EXPECT_FALSE(returned);
}
+// @brief Get Lease4 by DUID, IAID & subnet ID (2)
+//
+// Check that the system can cope with a DUID of any size.
+TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
+
+ // Create leases, although we need only one.
+ vector<Lease6Ptr> leases = createLeases6();
+
+ // Now add leases with increasing DUID size can be retrieved.
+ // For speed, go from 0 to 128 is steps of 16.
+ int duid_max = DUID::MAX_DUID_LEN;
+ EXPECT_EQ(128, duid_max);
+ for (uint8_t i = 0; i <= duid_max; i += 16) {
+ vector<uint8_t> duid_vec(i, i);
+ leases[1]->duid_.reset(new DUID(duid_vec));
+ EXPECT_TRUE(lmptr_->addLease(leases[1]));
+ Lease6Ptr returned = lmptr_->getLease6(*leases[1]->duid_,
+ leases[1]->iaid_,
+ leases[1]->subnet_id_);
+ ASSERT_TRUE(returned);
+ detailCompareLease(leases[1], returned);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
+ }
+
+ // Don't bother to check DUIDs longer than the maximum - these cannot be
+ // constructed, and that limitation is tested in the DUID/Client ID unit
+ // tests.
+}
+
+/// @brief Lease4 update tests
+///
+/// Checks that we are able to update a lease in the database.
+TEST_F(MySqlLeaseMgrTest, updateLease4) {
+ // Get the leases to be used for the test and add them to the database.
+ vector<Lease4Ptr> leases = createLeases4();
+ for (int i = 0; i < leases.size(); ++i) {
+ EXPECT_TRUE(lmptr_->addLease(leases[i]));
+ }
+
+ // Modify some fields in lease 1 (not the address) and update it.
+ ++leases[1]->subnet_id_;
+ leases[1]->valid_lft_ *= 2;
+ lmptr_->updateLease4(leases[1]);
+ // ... and check what is returned is what is expected.
+ Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
-// @brief Lease6 Update Tests
-//
-// Checks that we are able to update a lease in the database.
+ // Alter the lease again and check.
+ ++leases[1]->subnet_id_;
+ leases[1]->cltt_ += 6;
+ lmptr_->updateLease4(leases[1]);
+
+ // Explicitly clear the returned pointer before getting new data to ensure
+ // that the new data is returned.
+ l_returned.reset();
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Check we can do an update without changing data.
+ lmptr_->updateLease4(leases[1]);
+ l_returned.reset();
+ l_returned = lmptr_->getLease4(ioaddress4_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
+
+ // Try updating a lease not in the database.
+ lmptr_->deleteLease(ioaddress4_[2]);
+ EXPECT_THROW(lmptr_->updateLease4(leases[2]), isc::dhcp::NoSuchLease);
+}
+
+/// @brief Lease6 update tests
+///
+/// Checks that we are able to update a lease in the database.
TEST_F(MySqlLeaseMgrTest, updateLease6) {
// Get the leases to be used for the test.
vector<Lease6Ptr> leases = createLeases6();
- EXPECT_LE(3, leases.size()); // Expect to access leases 0 through 5
+ ASSERT_LE(3, leases.size()); // Expect to access leases 0 through 2
// Add a lease to the database and check that the lease is there.
EXPECT_TRUE(lmptr_->addLease(leases[1]));
lmptr_->commit();
- reopen();
- Lease6Ptr l_returned = lmptr_->getLease6(L1_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[1], l_returned);
+ Lease6Ptr l_returned = lmptr_->getLease6(ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
// Modify some fields in lease 1 (not the address) and update it.
++leases[1]->iaid_;
@@ -720,13 +1255,12 @@ TEST_F(MySqlLeaseMgrTest, updateLease6) {
leases[1]->valid_lft_ *= 2;
lmptr_->updateLease6(leases[1]);
lmptr_->commit();
- reopen();
// ... and check what is returned is what is expected.
l_returned.reset();
- l_returned = lmptr_->getLease6(L1_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[1], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
// Alter the lease again and check.
++leases[1]->iaid_;
@@ -736,16 +1270,16 @@ TEST_F(MySqlLeaseMgrTest, updateLease6) {
lmptr_->updateLease6(leases[1]);
l_returned.reset();
- l_returned = lmptr_->getLease6(L1_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[1], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
// Check we can do an update without changing data.
lmptr_->updateLease6(leases[1]);
l_returned.reset();
- l_returned = lmptr_->getLease6(L1_IOADDRESS);
- EXPECT_TRUE(l_returned);
- detailCompareLease6(leases[1], l_returned);
+ l_returned = lmptr_->getLease6(ioaddress6_[1]);
+ ASSERT_TRUE(l_returned);
+ detailCompareLease(leases[1], l_returned);
// Try updating a lease not in the database.
EXPECT_THROW(lmptr_->updateLease6(leases[2]), isc::dhcp::NoSuchLease);
diff --git a/src/lib/dhcpsrv/tests/option_space_unittest.cc b/src/lib/dhcpsrv/tests/option_space_unittest.cc
new file mode 100644
index 0000000..f8d75c8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/option_space_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcpsrv/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+ // Create some option space.
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Create another object with different values
+ // to check that the values will change.
+ OptionSpace space2("abc", false);
+ EXPECT_EQ("abc", space2.getName());
+ EXPECT_FALSE(space2.isVendorSpace());
+
+ // Verify that constructor throws exception if invalid
+ // option space name is provided.
+ EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overriden.
+TEST(OptionSpaceTest, setVendorSpace) {
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Override the vendor space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+ // Positive test scenarios: letters, digits, dashes, underscores
+ // lower/upper case allowed.
+ EXPECT_TRUE(OptionSpace::validateName("abc"));
+ EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+ EXPECT_TRUE(OptionSpace::validateName("1234"));
+ EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+ // Negative test scenarions: empty strings, dots, spaces are not
+ // allowed
+ EXPECT_FALSE(OptionSpace::validateName(""));
+ EXPECT_FALSE(OptionSpace::validateName(" "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc "));
+ EXPECT_FALSE(OptionSpace::validateName("isc "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ EXPECT_FALSE(OptionSpace::validateName("-isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc-"));
+ EXPECT_FALSE(OptionSpace::validateName("_isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+ // Test other special characters
+ const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+ '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+ for (int i = 0; i < sizeof(specials); ++i) {
+ std::ostringstream stream;
+ // Concatenate valid option space name: "abc" with an invalid character.
+ // That way we get option space names like: "abc!", "abc$" etc. It is
+ // expected that the validating function fails form them.
+ stream << "abc" << specials[i];
+ EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+ << "Test failed for special character '" << specials[i] << "'.";
+ }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+ // Create some option space and do not specify enterprise number.
+ // In such case the vendor space flag is expected to be
+ // set to false.
+ OptionSpace6 space1("abcd");
+ EXPECT_EQ("abcd", space1.getName());
+ EXPECT_FALSE(space1.isVendorSpace());
+
+ // Create an option space and specify an enterprise number. In this
+ // case the vendor space flag is expected to be set to true and the
+ // enterprise number should be set to a desired value.
+ OptionSpace6 space2("abcd", 2145);
+ EXPECT_EQ("abcd", space2.getName());
+ EXPECT_TRUE(space2.isVendorSpace());
+ EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+ // Verify that constructors throw an exception when invalid option
+ // space name has been specified.
+ EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+ EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+ OptionSpace6 space("isc");
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_FALSE(space.isVendorSpace());
+
+ // Mark it vendor option space and set enterprise id.
+ space.setVendorSpace(1234);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+ // Override the enterprise number to make sure and make sure that
+ // the new number is returned by the object.
+ space.setVendorSpace(2345);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+ // Clear the vendor option space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/schema_copy.h b/src/lib/dhcpsrv/tests/schema_copy.h
index 1dd796d..48a11ca 100644
--- a/src/lib/dhcpsrv/tests/schema_copy.h
+++ b/src/lib/dhcpsrv/tests/schema_copy.h
@@ -25,7 +25,9 @@ namespace {
// by semicolons, and the strings must end with a comma. The final line
// statement must be NULL (not in quotes)
-// THIS MUST BE KEPT UP TO DATE AND UPDATED IF THE SCHEMA CHANGES
+// NOTE: This file mirrors the schema in src/lib/dhcpsrv/dhcpdb_create.mysql.
+// If this file is altered, please ensure that any change is compatible
+// with the schema in dhcpdb_create.mysql.
// Deletion of existing tables.
@@ -44,13 +46,13 @@ const char* create_statement[] = {
"address INT UNSIGNED PRIMARY KEY NOT NULL,"
"hwaddr VARBINARY(20),"
"client_id VARBINARY(128),"
- "lease_time INT UNSIGNED,"
+ "valid_lifetime INT UNSIGNED,"
"expire TIMESTAMP,"
"subnet_id INT UNSIGNED"
") ENGINE = INNODB",
"CREATE TABLE lease6 ("
- "address VARCHAR(40) PRIMARY KEY NOT NULL,"
+ "address VARCHAR(39) PRIMARY KEY NOT NULL,"
"duid VARBINARY(128),"
"valid_lifetime INT UNSIGNED,"
"expire TIMESTAMP,"
@@ -75,7 +77,7 @@ const char* create_statement[] = {
"minor INT"
")",
- "INSERT INTO schema_version VALUES (0, 1)",
+ "INSERT INTO schema_version VALUES (1, 0)",
NULL
};
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9ebef9c..9f06c89 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -61,28 +61,28 @@ TEST(Subnet4Test, Pool4InSubnet4) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
- Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
- Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
- Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+ PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool4Ptr mypool = subnet->getPool4();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool4(pool2);
- subnet->addPool4(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool4();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+ mypool = subnet->getPool(IOAddress("192.1.2.195"));
EXPECT_EQ(mypool, pool3);
@@ -94,16 +94,16 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
- EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
- EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
}
TEST(Subnet4Test, addInvalidOption) {
@@ -115,13 +115,15 @@ TEST(Subnet4Test, addInvalidOption) {
// Create option with invalid universe (V6 instead of V4).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp4"),
+ isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+ isc::BadValue);
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -130,7 +132,7 @@ TEST(Subnet4Test, inRangeinPool) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
@@ -205,28 +207,28 @@ TEST(Subnet6Test, Pool6InSubnet6) {
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
- Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
+ PoolPtr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ PoolPtr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
+ PoolPtr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool6Ptr mypool = subnet->getPool6();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool6(pool2);
- subnet->addPool6(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool6();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
+ mypool = subnet->getPool(IOAddress("2001:db8:1:3::dead:beef"));
EXPECT_EQ(mypool, pool3);
}
@@ -237,21 +239,21 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::"), 48));
- EXPECT_THROW(subnet->addPool6(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
- EXPECT_THROW(subnet->addPool6(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
- EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+ EXPECT_THROW(subnet->addPool(pool4), BadValue);
}
TEST(Subnet6Test, addOptions) {
@@ -261,26 +263,59 @@ TEST(Subnet6Test, addOptions) {
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "isc"));
}
// Get options from the Subnet and check if all 10 are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(10, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
- // Validate codes of added options.
+ // Validate codes of options added to dhcp6 option space.
uint16_t expected_code = 100;
- for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
- option_desc != options.end(); ++option_desc) {
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
ASSERT_TRUE(option_desc->option);
EXPECT_EQ(expected_code, option_desc->option->getType());
++expected_code;
}
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getOptionDescriptors("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ // Delete options from all spaces.
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ // Make sure that all options have been removed.
+ options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
}
TEST(Subnet6Test, addNonUniqueOptions) {
@@ -292,19 +327,19 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
}
}
// Sanity check that all options are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(20, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(20, options->size());
// Use container index #1 to get the options by their codes.
- Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
- // For each code we should get two instances of options.
+ // For each code we should get two instances of options->
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(code);
@@ -329,8 +364,8 @@ TEST(Subnet6Test, addNonUniqueOptions) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
}
TEST(Subnet6Test, addInvalidOption) {
@@ -342,13 +377,13 @@ TEST(Subnet6Test, addInvalidOption) {
// Create option with invalid universe (V4 instead of V6).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp6"), isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
}
TEST(Subnet6Test, addPersistentOption) {
@@ -367,24 +402,24 @@ TEST(Subnet6Test, addPersistentOption) {
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
- ASSERT_NO_THROW(subnet->addOption(option, persistent));
+ ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
}
// Get added options from the subnet.
- Subnet::OptionContainer options = subnet->getOptions();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
- // options.get<2> returns reference to container index #2. This
+ // options->get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
- Subnet::OptionContainerPersistIndex& idx = options.get<2>();
+ Subnet::OptionContainerPersistIndex& idx = options->get<2>();
- // Get all persistent options.
+ // Get all persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
idx.equal_range(true);
// 3 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
- // Get all non-persistent options.
+ // Get all non-persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
idx.equal_range(false);
@@ -393,8 +428,33 @@ TEST(Subnet6Test, addPersistentOption) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
+}
+
+TEST(Subnet6Test, getOptionDescriptor) {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 56, 1, 2, 3, 4));
+
+ // Add 10 options to a "dhcp6" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Check that we can get each added option descriptor using
+ // individually.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+ // Returned descriptor should contain NULL option ptr.
+ EXPECT_FALSE(desc.option);
+ // Now, try the valid option space.
+ desc = subnet->getOptionDescriptor("dhcp6", code);
+ // Test that the option code matches the expected code.
+ ASSERT_TRUE(desc.option);
+ EXPECT_EQ(code, desc.option->getType());
+ }
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -404,7 +464,7 @@ TEST(Subnet6Test, inRangeinPool) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
IOAddress("2001:db8::20")));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
@@ -445,4 +505,15 @@ TEST(Subnet6Test, get) {
EXPECT_EQ(32, subnet.get().second);
}
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ subnet.setIface("en1");
+ EXPECT_EQ("en1", subnet.getIface());
+}
+
};
diff --git a/src/lib/dhcpsrv/tests/test_utils.cc b/src/lib/dhcpsrv/tests/test_utils.cc
new file mode 100644
index 0000000..3c69dbe
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_utils.cc
@@ -0,0 +1,58 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "test_utils.h"
+#include <gtest/gtest.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second) {
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_TRUE(first->hwaddr_ == second->hwaddr_);
+ EXPECT_TRUE(*first->client_id_ == *second->client_id_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+}
+
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second) {
+ EXPECT_EQ(first->type_, second->type_);
+
+ // Compare address strings. Comparison of address objects is not used, as
+ // odd things happen when they are different: the EXPECT_EQ macro appears to
+ // call the operator uint32_t() function, which causes an exception to be
+ // thrown for IPv6 addresses.
+ EXPECT_EQ(first->addr_.toText(), second->addr_.toText());
+ EXPECT_EQ(first->prefixlen_, second->prefixlen_);
+ EXPECT_EQ(first->iaid_, second->iaid_);
+ ASSERT_TRUE(first->duid_);
+ ASSERT_TRUE(second->duid_);
+ EXPECT_TRUE(*first->duid_ == *second->duid_);
+ EXPECT_EQ(first->preferred_lft_, second->preferred_lft_);
+ EXPECT_EQ(first->valid_lft_, second->valid_lft_);
+ EXPECT_EQ(first->cltt_, second->cltt_);
+ EXPECT_EQ(first->subnet_id_, second->subnet_id_);
+}
+
+};
+};
+};
diff --git a/src/lib/dhcpsrv/tests/test_utils.h b/src/lib/dhcpsrv/tests/test_utils.h
new file mode 100644
index 0000000..46df9fc
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/test_utils.h
@@ -0,0 +1,49 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LIBDHCPSRV_TEST_UTILS_H
+#define LIBDHCPSRV_TEST_UTILS_H
+
+#include <dhcpsrv/lease_mgr.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+// @brief performs details comparison between two IPv6 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease6Ptr& first, const Lease6Ptr& second);
+
+// @brief performs details comparison between two IPv4 leases
+//
+// @param first first lease to compare
+// @param second second lease to compare
+//
+// This method is intended to be run from gtest tests as it
+// uses gtest macros and possibly reports gtest failures.
+void
+detailCompareLease(const Lease4Ptr& first, const Lease4Ptr& second);
+
+
+};
+};
+};
+
+#endif
diff --git a/src/lib/dhcpsrv/triplet.h b/src/lib/dhcpsrv/triplet.h
index d9388fe..c7b8156 100644
--- a/src/lib/dhcpsrv/triplet.h
+++ b/src/lib/dhcpsrv/triplet.h
@@ -37,7 +37,7 @@ public:
///
/// Typically: uint32_t to Triplet assignment. It is very convenient
/// to be able to simply write Triplet<uint32_t> x = 7;
- Triplet<T> operator=(T other) {
+ Triplet<T>& operator=(T other) {
min_ = other;
default_ = other;
max_ = other;
diff --git a/src/lib/dhcpsrv/utils.h b/src/lib/dhcpsrv/utils.h
new file mode 100644
index 0000000..26d98ce
--- /dev/null
+++ b/src/lib/dhcpsrv/utils.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_UTILS_H
+#define DHCPSRV_UTILS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCPSRV_UTILS_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 14b74f7..286bd8c 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -21,6 +21,9 @@ EXTRA_DIST += rdata/ch_3/a_1.cc
EXTRA_DIST += rdata/ch_3/a_1.h
EXTRA_DIST += rdata/generic/cname_5.cc
EXTRA_DIST += rdata/generic/cname_5.h
+EXTRA_DIST += rdata/generic/detail/char_string.cc
+EXTRA_DIST += rdata/generic/detail/char_string.h
+EXTRA_DIST += rdata/generic/detail/lexer_util.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -82,6 +85,8 @@ EXTRA_DIST += rdata/in_1/srv_33.h
#EXTRA_DIST += rdata/template.cc
#EXTRA_DIST += rdata/template.h
+noinst_SCRIPTS = gen-rdatacode.py
+
# auto-generate by gen-rdatacode.py:
BUILT_SOURCES = rrclass.h rrtype.h rrparamregistry.cc
BUILT_SOURCES += rdataclass.h rdataclass.cc
@@ -91,6 +96,7 @@ lib_LTLIBRARIES = libb10-dns++.la
libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
libb10_dns___la_SOURCES =
+libb10_dns___la_SOURCES += dns_fwd.h
libb10_dns___la_SOURCES += edns.h edns.cc
libb10_dns___la_SOURCES += exceptions.h exceptions.cc
libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
@@ -98,6 +104,7 @@ libb10_dns___la_SOURCES += labelsequence.h labelsequence.cc
libb10_dns___la_SOURCES += masterload.h masterload.cc
libb10_dns___la_SOURCES += master_lexer.h master_lexer.cc
libb10_dns___la_SOURCES += master_lexer_state.h
+libb10_dns___la_SOURCES += master_loader.h master_loader.cc
libb10_dns___la_SOURCES += message.h message.cc
libb10_dns___la_SOURCES += messagerenderer.h messagerenderer.cc
libb10_dns___la_SOURCES += name.h name.cc
@@ -112,13 +119,20 @@ libb10_dns___la_SOURCES += rrparamregistry.h
libb10_dns___la_SOURCES += rrset.h rrset.cc
libb10_dns___la_SOURCES += rrttl.h rrttl.cc
libb10_dns___la_SOURCES += rrtype.cc
+libb10_dns___la_SOURCES += rrcollator.h rrcollator.cc
libb10_dns___la_SOURCES += question.h question.cc
libb10_dns___la_SOURCES += serial.h serial.cc
libb10_dns___la_SOURCES += tsig.h tsig.cc
libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
-libb10_dns___la_SOURCES += character_string.h character_string.cc
+libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
+libb10_dns___la_SOURCES += master_loader.h
+libb10_dns___la_SOURCES += rrset_collection_base.h
+libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
+libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.cc
libb10_dns___la_SOURCES += rdata/generic/detail/nsec3param_common.cc
@@ -140,16 +154,19 @@ rrclass.h: rrclass-placeholder.h
rrtype.h: rrtype-placeholder.h
rrparamregistry.cc: rrparamregistry-placeholder.cc
rrclass.h rrtype.h rrparamregistry.cc rdataclass.h rdataclass.cc: Makefile
- ./gen-rdatacode.py
+ $(PYTHON) ./gen-rdatacode.py
libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
libdns___include_HEADERS = \
edns.h \
exceptions.h \
+ dns_fwd.h \
labelsequence.h \
message.h \
- master_lexer.h \
masterload.h \
+ master_lexer.h \
+ master_loader.h \
+ master_loader_callbacks.h \
messagerenderer.h \
name.h \
question.h \
@@ -158,8 +175,11 @@ libdns___include_HEADERS = \
rdata.h \
rrparamregistry.h \
rrset.h \
+ rrset_collection_base.h \
+ rrset_collection.h \
rrttl.h \
- tsigkey.h
+ tsigkey.h \
+ zone_checker.h
# Purposely not installing these headers:
# name_internal.h: used only internally, and not actually DNS specific
# rdata/*/detail/*.h: these are internal use only
diff --git a/src/lib/dns/character_string.cc b/src/lib/dns/character_string.cc
deleted file mode 100644
index 3a289ac..0000000
--- a/src/lib/dns/character_string.cc
+++ /dev/null
@@ -1,140 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "character_string.h"
-#include "rdata.h"
-
-using namespace std;
-using namespace isc::dns::rdata;
-
-namespace isc {
-namespace dns {
-
-namespace {
-bool isDigit(char c) {
- return (('0' <= c) && (c <= '9'));
-}
-}
-
-std::string
-characterstr::getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator)
-{
- string result;
-
- // If the input string only contains white-spaces, it is an invalid
- // <character-string>
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText, "Invalid text format, \
- <character-string> field is missing.");
- }
-
- // Whether the <character-string> is separated with double quotes (")
- bool quotes_separated = (*input_iterator == '"');
- // Whether the quotes are pared if the string is quotes separated
- bool quotes_paired = false;
-
- if (quotes_separated) {
- ++input_iterator;
- }
-
- while(input_iterator < input_str.end()){
- // Escaped characters processing
- if (*input_iterator == '\\') {
- if (input_iterator + 1 == input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- if (isDigit(*(input_iterator + 1))) {
- // \DDD where each D is a digit. It its the octet
- // corresponding to the decimal number described by DDD
- if (input_iterator + 3 >= input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- int n = 0;
- ++input_iterator;
- for (int i = 0; i < 3; ++i) {
- if (isDigit(*input_iterator)) {
- n = n*10 + (*input_iterator - '0');
- ++input_iterator;
- } else {
- isc_throw(InvalidRdataText, "Illegal decimal \
- escaping series");
- }
- }
- if (n > 255) {
- isc_throw(InvalidRdataText, "Illegal octet \
- number");
- }
- result.push_back(n);
- continue;
- }
- } else {
- ++input_iterator;
- result.push_back(*input_iterator);
- ++input_iterator;
- continue;
- }
- }
- }
-
- if (quotes_separated) {
- // If the <character-string> is seperated with quotes symbol and
- // another quotes symbol is encountered, it is the end of the
- // <character-string>
- if (*input_iterator == '"') {
- quotes_paired = true;
- ++input_iterator;
- // Reach the end of character string
- break;
- }
- } else if (*input_iterator == ' ') {
- // If the <character-string> is not seperated with quotes symbol,
- // it is seperated with <space> char
- break;
- }
-
- result.push_back(*input_iterator);
-
- ++input_iterator;
- }
-
- if (result.size() > MAX_CHARSTRING_LEN) {
- isc_throw(CharStringTooLong, "<character-string> is too long");
- }
-
- if (quotes_separated && !quotes_paired) {
- isc_throw(InvalidRdataText, "The quotes are not paired");
- }
-
- return (result);
-}
-
-std::string
-characterstr::getNextCharacterString(util::InputBuffer& buffer, size_t len) {
- uint8_t str_len = buffer.readUint8();
-
- size_t pos = buffer.getPosition();
- if (len - pos < str_len) {
- isc_throw(InvalidRdataLength, "Invalid string length");
- }
-
- uint8_t buf[MAX_CHARSTRING_LEN];
- buffer.readData(buf, str_len);
- return (string(buf, buf + str_len));
-}
-
-} // end of namespace dns
-} // end of namespace isc
diff --git a/src/lib/dns/character_string.h b/src/lib/dns/character_string.h
deleted file mode 100644
index 2a68778..0000000
--- a/src/lib/dns/character_string.h
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CHARACTER_STRING_H
-#define CHARACTER_STRING_H
-
-#include <string>
-#include <exceptions/exceptions.h>
-#include <util/buffer.h>
-
-namespace isc {
-namespace dns {
-
-// \brief Some utility functions to extract <character-string> from string
-// or InputBuffer
-//
-// <character-string> is expressed in one or two ways: as a contiguous set
-// of characters without interior spaces, or as a string beginning with a "
-// and ending with a ". Inside a " delimited string any character can
-// occur, except for a " itself, which must be quoted using \ (back slash).
-// Ref. RFC1035
-
-
-namespace characterstr {
- /// Get a <character-string> from a string
- ///
- /// \param input_str The input string
- /// \param input_iterator The iterator from which to start extracting,
- /// the iterator will be updated to new position after the function
- /// is returned
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator);
-
- /// Get a <character-string> from a input buffer
- ///
- /// \param buffer The input buffer
- /// \param len The input buffer total length
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(util::InputBuffer& buffer, size_t len);
-
-} // namespace characterstr
-} // namespace dns
-} // namespace isc
-
-#endif // CHARACTER_STRING_H
diff --git a/src/lib/dns/dns_fwd.h b/src/lib/dns/dns_fwd.h
new file mode 100644
index 0000000..df71388
--- /dev/null
+++ b/src/lib/dns/dns_fwd.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_FWD_H
+#define DNS_FWD_H 1
+
+/// \file dns_fwd.h
+/// \brief Forward declarations for definitions of libdns++
+///
+/// This file provides a set of forward declarations for definitions commonly
+/// used in libdns++ to help minimize dependency when actual the definition
+/// is not necessary.
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MasterLoader;
+class MasterLoaderCallbacks;
+class Message;
+class AbstractMessageRenderer;
+class MessageRenderer;
+class NSEC3Hash;
+class NSEC3HashCreator;
+class Opcode;
+class Question;
+class Rcode;
+namespace rdata {
+class Rdata;
+}
+class RRCollator;
+class RRClass;
+class RRType;
+class RRTTL;
+class AbstractRRset;
+class RdataIterator;
+class RRsetCollectionBase;
+class RRsetCollection;
+class Serial;
+class TSIGContext;
+class TSIGError;
+class TSIGKey;
+class TSIGKeyRing;
+class TSIGRecord;
+
+} // namespace dns
+} // namespace isc
+#endif // DNS_FWD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index e51dfc5..fc63d73 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -24,6 +24,22 @@ from os.path import getmtime
import re
import sys
+# new_rdata_factory_users[] is a list of tuples of the form (rrtype,
+# rrclass). Items in the list use the (new) RdataFactory class, and
+# items which are not in the list use OldRdataFactory class.
+# Note: rrtype and rrclass must be specified in lowercase in
+# new_rdata_factory_users.
+#
+# Example:
+# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
+new_rdata_factory_users = [('aaaa', 'in'),
+ ('hinfo', 'generic'),
+ ('naptr', 'generic'),
+ ('soa', 'generic'),
+ ('spf', 'generic'),
+ ('txt', 'generic')
+ ]
+
re_typecode = re.compile('([\da-z]+)_(\d+)')
classcode2txt = {}
typecode2txt = {}
@@ -116,6 +132,9 @@ class AbstractMessageRenderer;\n\n'''
explicit ''' + type_utxt + '''(const std::string& type_str);
''' + type_utxt + '''(isc::util::InputBuffer& buffer, size_t rdata_len);
''' + type_utxt + '''(const ''' + type_utxt + '''& other);
+ ''' + type_utxt + '''(
+ MasterLexer& lexer, const Name* name,
+ MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
virtual std::string toText() const;
virtual void toWire(isc::util::OutputBuffer& buffer) const;
virtual void toWire(AbstractMessageRenderer& renderer) const;
@@ -203,17 +222,33 @@ def generate_rdatadef(file, basemtime):
rdata_deffile.write(class_definitions)
rdata_deffile.close()
-def generate_rdatahdr(file, declarations, basemtime):
+def generate_rdatahdr(file, heading, declarations, basemtime):
if not need_generate(file, basemtime):
print('skip generating ' + file);
return
+ heading += '''
+#ifndef DNS_RDATACLASS_H
+#define DNS_RDATACLASS_H 1
+
+#include <dns/master_loader.h>
+
+namespace isc {
+namespace dns {
+class Name;
+class MasterLexer;
+class MasterLoaderCallbacks;
+}
+}
+'''
declarations += '''
+#endif // DNS_RDATACLASS_H
+
// Local Variables:
// mode: c++
// End:
'''
rdata_header = open(file, 'w')
- rdata_header.write(heading_txt)
+ rdata_header.write(heading)
rdata_header.write(declarations)
rdata_header.close()
@@ -271,15 +306,29 @@ def generate_rrparam(fileprefix, basemtime):
class_utxt = class_tuple[1].upper()
indent = ' ' * 8
typeandclassparams += indent
+
+ # By default, we use OldRdataFactory (see bug #2497). If you
+ # want to pick RdataFactory for a particular type, add it to
+ # new_rdata_factory_users. Note that we explicitly generate (for
+ # optimization) class-independent ("generic") factories for class IN
+ # for optimization.
+ if (((type_txt.lower(), class_txt.lower()) in
+ new_rdata_factory_users) or
+ ((class_txt.lower() == 'in') and
+ ((type_txt.lower(), 'generic') in new_rdata_factory_users))):
+ rdf_class = 'RdataFactory'
+ else:
+ rdf_class = 'OldRdataFactory'
+
if class_tuple[1] != 'generic':
typeandclassparams += 'add("' + type_utxt + '", '
typeandclassparams += str(type_code) + ', "' + class_utxt
typeandclassparams += '", ' + str(class_code)
- typeandclassparams += ', RdataFactoryPtr(new RdataFactory<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
else:
typeandclassparams += 'add("' + type_utxt + '", ' + str(type_code)
- typeandclassparams += ', RdataFactoryPtr(new RdataFactory<'
+ typeandclassparams += ', RdataFactoryPtr(new ' + rdf_class + '<'
typeandclassparams += class_txt + '::' + type_utxt + '>()));\n'
rrparam_temp = open(placeholder, 'r')
@@ -296,8 +345,8 @@ if __name__ == "__main__":
try:
import_definitions(classcode2txt, typecode2txt, typeandclass)
generate_rdatadef('@builddir@/rdataclass.cc', rdatadef_mtime)
- generate_rdatahdr('@builddir@/rdataclass.h', rdata_declarations,
- rdatahdr_mtime)
+ generate_rdatahdr('@builddir@/rdataclass.h', heading_txt,
+ rdata_declarations, rdatahdr_mtime)
generate_typeclasscode('rrtype', rdatahdr_mtime, typecode2txt, 'Type')
generate_typeclasscode('rrclass', classdir_mtime,
classcode2txt, 'Class')
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index 0978a8f..9563355 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -18,31 +18,58 @@
#include <dns/master_lexer_inputsource.h>
#include <dns/master_lexer_state.h>
+#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <bitset>
#include <cassert>
+#include <limits>
#include <string>
#include <vector>
namespace isc {
namespace dns {
+// The definition of SOURCE_SIZE_UNKNOWN. Note that we initialize it using
+// a method of another library. Technically, this could trigger a static
+// initialization fiasco. But in this particular usage it's very unlikely
+// to happen because this value is expected to be used only as a return
+// value of a MasterLexer's method, and its constructor needs definitions
+// here.
+const size_t MasterLexer::SOURCE_SIZE_UNKNOWN =
+ std::numeric_limits<size_t>::max();
+
namespace {
typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
-}
+} // end unnamed namespace
using namespace master_lexer_internal;
+
struct MasterLexer::MasterLexerImpl {
- MasterLexerImpl() : source_(NULL), token_(Token::NOT_STARTED),
- paren_count_(0), last_was_eol_(false)
- {}
+ MasterLexerImpl() : source_(NULL), token_(MasterToken::NOT_STARTED),
+ total_size_(0), popped_size_(0),
+ paren_count_(0), last_was_eol_(true),
+ has_previous_(false),
+ previous_paren_count_(0),
+ previous_was_eol_(false)
+ {
+ separators_.set('\r');
+ separators_.set('\n');
+ separators_.set(' ');
+ separators_.set('\t');
+ separators_.set('(');
+ separators_.set(')');
+ esc_separators_.set('\r');
+ esc_separators_.set('\n');
+ }
// A helper method to skip possible comments toward the end of EOL or EOF.
// commonly used by state classes. It returns the corresponding "end-of"
// character in case it's a comment; otherwise it simply returns the
// current character.
- int skipComment(int c) {
- if (c == ';') {
+ int skipComment(int c, bool escaped = false) {
+ if (c == ';' && !escaped) {
while (true) {
c = source_->getChar();
if (c == '\n' || c == InputSource::END_OF_STREAM) {
@@ -53,14 +80,56 @@ struct MasterLexer::MasterLexerImpl {
return (c);
}
+ bool isTokenEnd(int c, bool escaped) {
+ // Special case of EOF (end of stream); this is not in the bitmaps
+ if (c == InputSource::END_OF_STREAM) {
+ return (true);
+ }
+ // In this implementation we only ensure the behavior for unsigned
+ // range of characters, so we restrict the range of the values up to
+ // 0x7f = 127
+ return (escaped ? esc_separators_.test(c & 0x7f) :
+ separators_.test(c & 0x7f));
+ }
+
+ void setTotalSize() {
+ assert(source_ != NULL);
+ if (total_size_ != SOURCE_SIZE_UNKNOWN) {
+ const size_t current_size = source_->getSize();
+ if (current_size != SOURCE_SIZE_UNKNOWN) {
+ total_size_ += current_size;
+ } else {
+ total_size_ = SOURCE_SIZE_UNKNOWN;
+ }
+ }
+ }
+
std::vector<InputSourcePtr> sources_;
InputSource* source_; // current source (NULL if sources_ is empty)
- Token token_; // currently recognized token (set by a state)
+ MasterToken token_; // currently recognized token (set by a state)
+ std::vector<char> data_; // placeholder for string data
+
+ // Keep track of the total size of all sources and characters that have
+ // been read from sources already popped.
+ size_t total_size_; // accumulated size (# of chars) of sources
+ size_t popped_size_; // total size of sources that have been popped
// These are used in states, and defined here only as a placeholder.
// The main lexer class does not need these members.
size_t paren_count_; // nest count of the parentheses
bool last_was_eol_; // whether the lexer just passed an end-of-line
+
+ // Bitmaps that gives whether a given (positive) character should be
+ // considered a separator of a string/number token. The esc_ version
+ // is a subset of the other, excluding characters that can be ignored
+ // if escaped by a backslash. See isTokenEnd() for the bitmap size.
+ std::bitset<128> separators_;
+ std::bitset<128> esc_separators_;
+
+ // These are to allow restoring state before previous token.
+ bool has_previous_;
+ size_t previous_paren_count_;
+ bool previous_was_eol_;
};
MasterLexer::MasterLexer() : impl_(new MasterLexerImpl) {
@@ -86,13 +155,25 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
}
impl_->source_ = impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+ impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
return (true);
}
void
MasterLexer::pushSource(std::istream& input) {
- impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+ } catch (const InputSource::OpenError& ex) {
+ // Convert the "internal" exception to public one.
+ isc_throw(Unexpected, "Failed to push a stream to lexer: " <<
+ ex.what());
+ }
impl_->source_ = impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+ impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
}
void
@@ -101,9 +182,16 @@ MasterLexer::popSource() {
isc_throw(InvalidOperation,
"MasterLexer::popSource on an empty source");
}
+ impl_->popped_size_ += impl_->source_->getPosition();
impl_->sources_.pop_back();
impl_->source_ = impl_->sources_.empty() ? NULL :
impl_->sources_.back().get();
+ impl_->has_previous_ = false;
+}
+
+size_t
+MasterLexer::getSourceCount() const {
+ return (impl_->sources_.size());
}
std::string
@@ -122,21 +210,131 @@ MasterLexer::getSourceLine() const {
return (impl_->sources_.back()->getCurrentLine());
}
+size_t
+MasterLexer::getTotalSourceSize() const {
+ return (impl_->total_size_);
+}
+
+size_t
+MasterLexer::getPosition() const {
+ size_t position = impl_->popped_size_;
+ BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+ position += src->getPosition();
+ }
+ return (position);
+}
+
+const MasterToken&
+MasterLexer::getNextToken(Options options) {
+ if (impl_->source_ == NULL) {
+ isc_throw(isc::InvalidOperation, "No source to read tokens from");
+ }
+ // Store the current state so we can restore it in ungetToken
+ impl_->previous_paren_count_ = impl_->paren_count_;
+ impl_->previous_was_eol_ = impl_->last_was_eol_;
+ impl_->source_->mark();
+ impl_->has_previous_ = true;
+ // Reset the token now. This is to check a token was actually produced.
+ // This is debugging aid.
+ impl_->token_ = MasterToken(MasterToken::NO_TOKEN_PRODUCED);
+ // And get the token
+
+ // This actually handles EOF internally too.
+ const State* state = State::start(*this, options);
+ if (state != NULL) {
+ state->handle(*this);
+ }
+ // Make sure a token was produced. Since this Can Not Happen, we assert
+ // here instead of throwing.
+ assert(impl_->token_.getType() != MasterToken::ERROR ||
+ impl_->token_.getErrorCode() != MasterToken::NO_TOKEN_PRODUCED);
+ return (impl_->token_);
+}
+
+namespace {
+inline MasterLexer::Options
+optionsForTokenType(MasterToken::Type expect) {
+ switch (expect) {
+ case MasterToken::STRING:
+ return (MasterLexer::NONE);
+ case MasterToken::QSTRING:
+ return (MasterLexer::QSTRING);
+ case MasterToken::NUMBER:
+ return (MasterLexer::NUMBER);
+ default:
+ isc_throw(InvalidParameter,
+ "expected type for getNextToken not supported: " << expect);
+ }
+}
+}
+
+const MasterToken&
+MasterLexer::getNextToken(MasterToken::Type expect, bool eol_ok) {
+ // Get the next token, specifying an appropriate option corresponding to
+ // the expected type. The result should be set in impl_->token_.
+ getNextToken(optionsForTokenType(expect));
+
+ if (impl_->token_.getType() == MasterToken::ERROR) {
+ if (impl_->token_.getErrorCode() == MasterToken::NUMBER_OUT_OF_RANGE) {
+ ungetToken();
+ }
+ throw LexerError(__FILE__, __LINE__, impl_->token_);
+ }
+
+ const bool is_eol_like =
+ (impl_->token_.getType() == MasterToken::END_OF_LINE ||
+ impl_->token_.getType() == MasterToken::END_OF_FILE);
+ if (eol_ok && is_eol_like) {
+ return (impl_->token_);
+ }
+ if (impl_->token_.getType() == MasterToken::STRING &&
+ expect == MasterToken::QSTRING) {
+ return (impl_->token_);
+ }
+ if (impl_->token_.getType() != expect) {
+ ungetToken();
+ if (is_eol_like) {
+ throw LexerError(__FILE__, __LINE__,
+ MasterToken(MasterToken::UNEXPECTED_END));
+ }
+ assert(expect == MasterToken::NUMBER);
+ throw LexerError(__FILE__, __LINE__,
+ MasterToken(MasterToken::BAD_NUMBER));
+ }
+
+ return (impl_->token_);
+}
+
+void
+MasterLexer::ungetToken() {
+ if (impl_->has_previous_) {
+ impl_->has_previous_ = false;
+ impl_->source_->ungetAll();
+ impl_->last_was_eol_ = impl_->previous_was_eol_;
+ impl_->paren_count_ = impl_->previous_paren_count_;
+ } else {
+ isc_throw(isc::InvalidOperation, "No token to unget ready");
+ }
+}
+
namespace {
const char* const error_text[] = {
"lexer not started", // NOT_STARTED
"unbalanced parentheses", // UNBALANCED_PAREN
"unexpected end of input", // UNEXPECTED_END
- "unbalanced quotes" // UNBALANCED_QUOTES
+ "unbalanced quotes", // UNBALANCED_QUOTES
+ "no token produced", // NO_TOKEN_PRODUCED
+ "number out of range", // NUMBER_OUT_OF_RANGE
+ "not a valid number" // BAD_NUMBER
};
const size_t error_text_max_count = sizeof(error_text) / sizeof(error_text[0]);
-}
+} // end unnamed namespace
std::string
-MasterLexer::Token::getErrorText() const {
+MasterToken::getErrorText() const {
if (type_ != ERROR) {
isc_throw(InvalidOperation,
- "Token::getErrorText() for non error type");
+ "MasterToken::getErrorText() for non error type");
}
// The class integrity ensures the following:
@@ -149,14 +347,12 @@ namespace master_lexer_internal {
// Note that these need to be defined here so that they can refer to
// the details of MasterLexerImpl.
-typedef MasterLexer::Token Token; // convenience shortcut
-
bool
State::wasLastEOL(const MasterLexer& lexer) const {
return (lexer.impl_->last_was_eol_);
}
-const MasterLexer::Token&
+const MasterToken&
State::getToken(const MasterLexer& lexer) const {
return (lexer.impl_->token_);
}
@@ -171,7 +367,7 @@ class CRLF : public State {
public:
CRLF() {}
virtual ~CRLF() {} // see the base class for the destructor
- virtual const State* handle(MasterLexer& lexer) const {
+ virtual void handle(MasterLexer& lexer) const {
// We've just seen '\r'. If this is part of a sequence of '\r\n',
// we combine them as a single END-OF-LINE. Otherwise we treat the
// single '\r' as an EOL and continue tokeniziation from the character
@@ -186,20 +382,30 @@ public:
if (c != '\n') {
getLexerImpl(lexer)->source_->ungetChar();
}
- getLexerImpl(lexer)->token_ = Token(Token::END_OF_LINE);
+ getLexerImpl(lexer)->token_ = MasterToken(MasterToken::END_OF_LINE);
getLexerImpl(lexer)->last_was_eol_ = true;
- return (NULL);
}
};
-// Currently this is provided mostly as a place holder
class String : public State {
public:
String() {}
virtual ~String() {} // see the base class for the destructor
- virtual const State* handle(MasterLexer& /*lexer*/) const {
- return (NULL);
- }
+ virtual void handle(MasterLexer& lexer) const;
+};
+
+class QString : public State {
+public:
+ QString() {}
+ virtual ~QString() {} // see the base class for the destructor
+ virtual void handle(MasterLexer& lexer) const;
+};
+
+class Number : public State {
+public:
+ Number() {}
+ virtual ~Number() {}
+ virtual void handle(MasterLexer& lexer) const;
};
// We use a common instance of a each state in a singleton-like way to save
@@ -209,7 +415,9 @@ public:
// this file.
const CRLF CRLF_STATE;
const String STRING_STATE;
-}
+const QString QSTRING_STATE;
+const Number NUMBER_STATE;
+} // end unnamed namespace
const State&
State::getInstance(ID state_id) {
@@ -218,6 +426,10 @@ State::getInstance(ID state_id) {
return (CRLF_STATE);
case String:
return (STRING_STATE);
+ case QString:
+ return (QSTRING_STATE);
+ case Number:
+ return (NUMBER_STATE);
}
// This is a bug of the caller, and this method is only expected to be
@@ -233,47 +445,59 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
MasterLexer::MasterLexerImpl& lexerimpl = *lexer.impl_;
size_t& paren_count = lexerimpl.paren_count_;
+ // Note: the if-else in the loop is getting complicated. When we complete
+ // #2374, revisit the organization to see if we need a fundamental
+ // refactoring.
while (true) {
const int c = lexerimpl.skipComment(lexerimpl.source_->getChar());
if (c == InputSource::END_OF_STREAM) {
lexerimpl.last_was_eol_ = false;
if (paren_count != 0) {
- lexerimpl.token_ = Token(Token::UNBALANCED_PAREN);
+ lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
paren_count = 0; // reset to 0; this helps in lenient mode.
return (NULL);
}
- lexerimpl.token_ = Token(Token::END_OF_FILE);
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_FILE);
return (NULL);
} else if (c == ' ' || c == '\t') {
// If requested and we are not in (), recognize the initial space.
if (lexerimpl.last_was_eol_ && paren_count == 0 &&
(options & MasterLexer::INITIAL_WS) != 0) {
lexerimpl.last_was_eol_ = false;
- lexerimpl.token_ = Token(Token::INITIAL_WS);
+ lexerimpl.token_ = MasterToken(MasterToken::INITIAL_WS);
return (NULL);
}
} else if (c == '\n') {
lexerimpl.last_was_eol_ = true;
if (paren_count == 0) { // we don't recognize EOL if we are in ()
- lexerimpl.token_ = Token(Token::END_OF_LINE);
+ lexerimpl.token_ = MasterToken(MasterToken::END_OF_LINE);
return (NULL);
}
} else if (c == '\r') {
if (paren_count == 0) { // check if we are in () (see above)
return (&CRLF_STATE);
}
+ } else if (c == '"' && (options & MasterLexer::QSTRING) != 0) {
+ lexerimpl.last_was_eol_ = false;
+ return (&QSTRING_STATE);
} else if (c == '(') {
lexerimpl.last_was_eol_ = false;
++paren_count;
} else if (c == ')') {
lexerimpl.last_was_eol_ = false;
if (paren_count == 0) {
- lexerimpl.token_ = Token(Token::UNBALANCED_PAREN);
+ lexerimpl.token_ = MasterToken(MasterToken::UNBALANCED_PAREN);
return (NULL);
}
--paren_count;
+ } else if ((options & MasterLexer::NUMBER) != 0 &&isdigit(c)) {
+ lexerimpl.last_was_eol_ = false;
+ // this character will be handled in the number state
+ lexerimpl.source_->ungetChar();
+ return (&NUMBER_STATE);
} else {
- // Note: in #2373 we should probably ungetChar().
+ // this character will be handled in the string state
+ lexerimpl.source_->ungetChar();
lexerimpl.last_was_eol_ = false;
return (&STRING_STATE);
}
@@ -281,6 +505,108 @@ State::start(MasterLexer& lexer, MasterLexer::Options options) {
}
}
+void
+String::handle(MasterLexer& lexer) const {
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+
+ bool escaped = false;
+ while (true) {
+ const int c = getLexerImpl(lexer)->skipComment(
+ getLexerImpl(lexer)->source_->getChar(), escaped);
+
+ if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
+ getLexerImpl(lexer)->source_->ungetChar();
+ // make sure it nul-terminated as a c-str (excluded from token
+ // data).
+ data.push_back('\0');
+ getLexerImpl(lexer)->token_ =
+ MasterToken(&data.at(0), data.size() - 1);
+ return;
+ }
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+}
+
+void
+QString::handle(MasterLexer& lexer) const {
+ MasterToken& token = getLexerImpl(lexer)->token_;
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+
+ bool escaped = false;
+ while (true) {
+ const int c = getLexerImpl(lexer)->source_->getChar();
+ if (c == InputSource::END_OF_STREAM) {
+ token = MasterToken(MasterToken::UNEXPECTED_END);
+ return;
+ } else if (c == '"') {
+ if (escaped) {
+ // found escaped '"'. overwrite the preceding backslash.
+ assert(!data.empty());
+ escaped = false;
+ data.back() = '"';
+ } else {
+ // make sure it nul-terminated as a c-str (excluded from token
+ // data). This also simplifies the case of an empty string.
+ data.push_back('\0');
+ token = MasterToken(&data.at(0), data.size() - 1, true);
+ return;
+ }
+ } else if (c == '\n' && !escaped) {
+ getLexerImpl(lexer)->source_->ungetChar();
+ token = MasterToken(MasterToken::UNBALANCED_QUOTES);
+ return;
+ } else {
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+ }
+}
+
+void
+Number::handle(MasterLexer& lexer) const {
+ MasterToken& token = getLexerImpl(lexer)->token_;
+
+ // It may yet turn out to be a string, so we first
+ // collect all the data
+ bool digits_only = true;
+ std::vector<char>& data = getLexerImpl(lexer)->data_;
+ data.clear();
+ bool escaped = false;
+
+ while (true) {
+ const int c = getLexerImpl(lexer)->skipComment(
+ getLexerImpl(lexer)->source_->getChar(), escaped);
+ if (getLexerImpl(lexer)->isTokenEnd(c, escaped)) {
+ getLexerImpl(lexer)->source_->ungetChar();
+ // We need to close the string whether it's digits-only (for
+ // lexical_cast) or not (see String::handle()).
+ data.push_back('\0');
+ if (digits_only) {
+ try {
+ const uint32_t number32 =
+ boost::lexical_cast<uint32_t, const char*>(&data[0]);
+ token = MasterToken(number32);
+ } catch (const boost::bad_lexical_cast&) {
+ // Since we already know we have only digits,
+ // range should be the only possible problem.
+ token = MasterToken(MasterToken::NUMBER_OUT_OF_RANGE);
+ }
+ } else {
+ token = MasterToken(&data.at(0), data.size() - 1);
+ }
+ return;
+ }
+ if (!isdigit(c)) {
+ digits_only = false;
+ }
+ escaped = (c == '\\' && !escaped);
+ data.push_back(c);
+ }
+}
+
} // namespace master_lexer_internal
} // end of namespace dns
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 854d602..31c6443 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -28,171 +28,6 @@ namespace master_lexer_internal {
class State;
}
-/// \brief Tokenizer for parsing DNS master files.
-///
-/// The \c MasterLexer class provides tokenize interfaces for parsing DNS
-/// master files. It understands some special rules of master files as
-/// defined in RFC 1035, such as comments, character escaping, and multi-line
-/// data, and provides the user application with the actual data in a
-/// more convenient form such as a std::string object.
-///
-/// In order to support the $INCLUDE notation, this class is designed to be
-/// able to operate on multiple files or input streams in the nested way.
-/// The \c pushSource() and \c popSource() methods correspond to the push
-/// and pop operations.
-///
-/// While this class is public, it is less likely to be used by normal
-/// applications; it's mainly expected to be used within this library,
-/// specifically by the \c MasterLoader class and \c Rdata implementation
-/// classes.
-///
-/// \note The error handling policy of this class is slightly different from
-/// that of other classes of this library. We generally throw an exception
-/// for an invalid input, whether it's more likely to be a program error or
-/// a "user error", which means an invalid input that comes from outside of
-/// the library. But, this class returns an error code for some certain
-/// types of user errors instead of throwing an exception. Such cases include
-/// a syntax error identified by the lexer or a misspelled file name that
-/// causes a system error at the time of open. This is based on the assumption
-/// that the main user of this class is a parser of master files, where
-/// we want to give an option to ignore some non fatal errors and continue
-/// the parsing. This will be useful if it just performs overall error
-/// checks on a master file. When the (immediate) caller needs to do explicit
-/// error handling, exceptions are not that a useful tool for error reporting
-/// because we cannot separate the normal and error cases anyway, which would
-/// be one major advantage when we use exceptions. And, exceptions are
-/// generally more expensive, either when it happens or just by being able
-/// to handle with \c try and \c catch (depending on the underlying
-/// implementation of the exception handling). For these reasons, some of
-/// this class does not throw for an error that would be reported as an
-/// exception in other classes.
-class MasterLexer {
- friend class master_lexer_internal::State;
-public:
- class Token; // we define it separately for better readability
-
- /// \brief Options for getNextToken.
- ///
- /// A compound option, indicating multiple options are set, can be
- /// specified using the logical OR operator (operator|()).
- enum Options {
- NONE = 0, ///< No option
- INITIAL_WS = 1, ///< recognize begin-of-line spaces after an
- ///< end-of-line
- QSTRING = 2, ///< recognize quoted string
- NUMBER = 4 ///< recognize numeric text as integer
- };
-
- /// \brief The constructor.
- ///
- /// \throw std::bad_alloc Internal resource allocation fails (rare case).
- MasterLexer();
-
- /// \brief The destructor.
- ///
- /// It internally closes any remaining input sources.
- ~MasterLexer();
-
- /// \brief Open a file and make it the current input source of MasterLexer.
- ///
- /// The opened file can be explicitly closed by the \c popSource() method;
- /// if \c popSource() is not called within the lifetime of the
- /// \c MasterLexer, it will be closed in the destructor.
- ///
- /// In the case possible system errors in opening the file (most likely
- /// because of specifying a non-existent or unreadable file), it returns
- /// false, and if the optional \c error parameter is non NULL, it will be
- /// set to a description of the error (any existing content of the string
- /// will be discarded). If opening the file succeeds, the given
- /// \c error parameter will be intact.
- ///
- /// Note that this method has two styles of error reporting: one by
- /// returning \c false (and setting \c error optionally) and the other
- /// by throwing an exception. See the note for the class description
- /// about the distinction.
- ///
- /// \throw InvalidParameter filename is NULL
- /// \param filename A non NULL string specifying a master file
- /// \param error If non null, a placeholder to set error description in
- /// case of failure.
- ///
- /// \return true if pushing the file succeeds; false otherwise.
- bool pushSource(const char* filename, std::string* error = NULL);
-
- /// \brief Make the given stream the current input source of MasterLexer.
- ///
- /// The caller still holds the ownership of the passed stream; it's the
- /// caller's responsibility to keep it valid as long as it's used in
- /// \c MasterLexer or to release any resource for the stream after that.
- /// The caller can explicitly tell \c MasterLexer to stop using the
- /// stream by calling the \c popSource() method.
- ///
- /// \param input An input stream object that produces textual
- /// representation of DNS RRs.
- void pushSource(std::istream& input);
-
- /// \brief Stop using the most recently opened input source (file or
- /// stream).
- ///
- /// If it's a file, the previously opened file will be closed internally.
- /// If it's a stream, \c MasterLexer will simply stop using
- /// the stream; the caller can assume it will be never used in
- /// \c MasterLexer thereafter.
- ///
- /// This method must not be called when there is no source pushed for
- /// \c MasterLexer. This method is otherwise exception free.
- ///
- /// \throw isc::InvalidOperation Called with no pushed source.
- void popSource();
-
- /// \brief Return the name of the current input source name.
- ///
- /// If it's a file, it will be the C string given at the corresponding
- /// \c pushSource() call, that is, its filename. If it's a stream, it will
- /// be formatted as \c "stream-%p" where \c %p is hex representation
- /// of the address of the stream object.
- ///
- /// If there is no opened source at the time of the call, this method
- /// returns an empty string.
- ///
- /// \throw std::bad_alloc Resource allocation failed for string
- /// construction (rare case)
- ///
- /// \return A string representation of the current source (see the
- /// description)
- std::string getSourceName() const;
-
- /// \brief Return the input source line number.
- ///
- /// If there is an opened source, the return value will be a non-0
- /// integer indicating the line number of the current source where
- /// the \c MasterLexer is currently working. The expected usage of
- /// this value is to print a helpful error message when parsing fails
- /// by specifically identifying the position of the error.
- ///
- /// If there is no opened source at the time of the call, this method
- /// returns 0.
- ///
- /// \throw None
- ///
- /// \return The current line number of the source (see the description)
- size_t getSourceLine() const;
-
-private:
- struct MasterLexerImpl;
- MasterLexerImpl* impl_;
-};
-
-/// \brief Operator to combine \c MasterLexer options
-///
-/// This is a trivial shortcut so that compound options can be specified
-/// in an intuitive way.
-inline MasterLexer::Options
-operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
- return (static_cast<MasterLexer::Options>(
- static_cast<unsigned>(o1) | static_cast<unsigned>(o2)));
-}
-
/// \brief Tokens for \c MasterLexer
///
/// This is a simple value-class encapsulating a type of a lexer token and
@@ -207,7 +42,7 @@ operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
/// (using the default version of copy constructor and assignment operator),
/// but it's mainly for internal implementation convenience. Applications will
/// simply refer to Token object as a reference via the \c MasterLexer class.
-class MasterLexer::Token {
+class MasterToken {
public:
/// \brief Enumeration for token types
///
@@ -216,10 +51,11 @@ public:
/// as an unsigned 32-bit integer. If we see the need for larger integers
/// or negative numbers, we can then extend the token types.
enum Type {
- END_OF_LINE, ///< End of line detected (if asked for detecting it)
- END_OF_FILE, ///< End of file detected (if asked for detecting it)
+ END_OF_LINE, ///< End of line detected
+ END_OF_FILE, ///< End of file detected
INITIAL_WS, ///< White spaces at the beginning of a line after an
- ///< end of line
+ ///< end of line or at the beginning of file (if asked
+ // for detecting it)
NOVALUE_TYPE_MAX = INITIAL_WS, ///< Max integer corresponding to
/// no-value (type only) types.
/// Mainly for internal use.
@@ -234,8 +70,12 @@ public:
NOT_STARTED, ///< The lexer is just initialized and has no token
UNBALANCED_PAREN, ///< Unbalanced parentheses detected
UNEXPECTED_END, ///< The lexer reaches the end of line or file
- /// unexpectedly
+ /// unexpectedly
UNBALANCED_QUOTES, ///< Unbalanced quotations detected
+ NO_TOKEN_PRODUCED, ///< No token was produced. This means programmer
+ /// error and should never get out of the lexer.
+ NUMBER_OUT_OF_RANGE, ///< Number was out of range
+ BAD_NUMBER, ///< Number is expected but not recognized
MAX_ERROR_CODE ///< Max integer corresponding to valid error codes.
/// (excluding this one). Mainly for internal use.
};
@@ -248,9 +88,15 @@ public:
///
/// Any character can be stored in the valid range of the region.
/// In particular, there can be a nul character (\0) in the middle of
- /// the region. On the other hand, it is not ensured that the string
- /// is nul-terminated. So the usual string manipulation API may not work
+ /// the region. So the usual string manipulation API may not work
/// as expected.
+ ///
+ /// The `MasterLexer` implementation ensures that there are at least
+ /// len + 1 bytes of valid memory region starting from beg, and that
+ /// beg[len] is \0. This means the application can use the bytes as a
+ /// validly nul-terminated C string if there is no intermediate nul
+ /// character. Note also that due to this property beg is always non
+ /// NULL; for an empty string len will be set to 0 and beg[0] is \0.
struct StringRegion {
const char* beg; ///< The start address of the string
size_t len; ///< The length of the string in bytes
@@ -261,7 +107,7 @@ public:
/// \throw InvalidParameter A value type token is specified.
/// \param type The type of the token. It must indicate a non-value
/// type (not larger than \c NOVALUE_TYPE_MAX).
- explicit Token(Type type) : type_(type) {
+ explicit MasterToken(Type type) : type_(type) {
if (type > NOVALUE_TYPE_MAX) {
isc_throw(InvalidParameter, "Token per-type constructor "
"called with invalid type: " << type);
@@ -283,7 +129,7 @@ public:
/// \param str_beg The start address of the string
/// \param str_len The size of the string in bytes
/// \param quoted true if it's a quoted string; false otherwise.
- Token(const char* str_beg, size_t str_len, bool quoted = false) :
+ MasterToken(const char* str_beg, size_t str_len, bool quoted = false) :
type_(quoted ? QSTRING : STRING)
{
val_.str_region_.beg = str_beg;
@@ -294,7 +140,7 @@ public:
///
/// \brief number An unsigned 32-bit integer corresponding to the token
/// value.
- explicit Token(uint32_t number) : type_(NUMBER) {
+ explicit MasterToken(uint32_t number) : type_(NUMBER) {
val_.number_ = number;
}
@@ -302,7 +148,7 @@ public:
///
/// \throw InvalidParameter Invalid error code value is specified.
/// \brief error_code A pre-defined constant of \c ErrorCode.
- explicit Token(ErrorCode error_code) : type_(ERROR) {
+ explicit MasterToken(ErrorCode error_code) : type_(ERROR) {
if (!(error_code < MAX_ERROR_CODE)) {
isc_throw(InvalidParameter, "Invalid master lexer error code: "
<< error_code);
@@ -340,12 +186,34 @@ public:
/// string object.
/// \return A std::string object corresponding to the string token value.
std::string getString() const {
+ std::string ret;
+ getString(ret);
+ return (ret);
+ }
+
+ /// \brief Fill in a string with the value of a string-variant token.
+ ///
+ /// This is similar to the other version of \c getString(), but
+ /// the caller is supposed to pass a placeholder string object.
+ /// This will be more efficient if the caller uses the same
+ /// \c MasterLexer repeatedly and needs to get string token in the
+ /// form of a string object many times as this version could reuse
+ /// the existing internal storage of the passed string.
+ ///
+ /// Any existing content of the passed string will be removed.
+ ///
+ /// \throw InvalidOperation Called on a non string-variant types of token.
+ /// \throw std::bad_alloc Resource allocation failure in constructing the
+ /// string object.
+ ///
+ /// \param ret A string object to be filled with the token string.
+ void getString(std::string& ret) const {
if (type_ != STRING && type_ != QSTRING) {
isc_throw(InvalidOperation,
"Token::getString() for non string-variant type");
}
- return (std::string(val_.str_region_.beg,
- val_.str_region_.beg + val_.str_region_.len));
+ ret.assign(val_.str_region_.beg,
+ val_.str_region_.beg + val_.str_region_.len);
}
/// \brief Return the value of a string-variant token as a string object.
@@ -397,6 +265,412 @@ private:
} val_;
};
+/// \brief Tokenizer for parsing DNS master files.
+///
+/// The \c MasterLexer class provides tokenize interfaces for parsing DNS
+/// master files. It understands some special rules of master files as
+/// defined in RFC 1035, such as comments, character escaping, and multi-line
+/// data, and provides the user application with the actual data in a
+/// more convenient form such as a std::string object.
+///
+/// In order to support the $INCLUDE notation, this class is designed to be
+/// able to operate on multiple files or input streams in the nested way.
+/// The \c pushSource() and \c popSource() methods correspond to the push
+/// and pop operations.
+///
+/// While this class is public, it is less likely to be used by normal
+/// applications; it's mainly expected to be used within this library,
+/// specifically by the \c MasterLoader class and \c Rdata implementation
+/// classes.
+///
+/// \note The error handling policy of this class is slightly different from
+/// that of other classes of this library. We generally throw an exception
+/// for an invalid input, whether it's more likely to be a program error or
+/// a "user error", which means an invalid input that comes from outside of
+/// the library. But, this class returns an error code for some certain
+/// types of user errors instead of throwing an exception. Such cases include
+/// a syntax error identified by the lexer or a misspelled file name that
+/// causes a system error at the time of open. This is based on the assumption
+/// that the main user of this class is a parser of master files, where
+/// we want to give an option to ignore some non fatal errors and continue
+/// the parsing. This will be useful if it just performs overall error
+/// checks on a master file. When the (immediate) caller needs to do explicit
+/// error handling, exceptions are not that a useful tool for error reporting
+/// because we cannot separate the normal and error cases anyway, which would
+/// be one major advantage when we use exceptions. And, exceptions are
+/// generally more expensive, either when it happens or just by being able
+/// to handle with \c try and \c catch (depending on the underlying
+/// implementation of the exception handling). For these reasons, some of
+/// this class does not throw for an error that would be reported as an
+/// exception in other classes.
+class MasterLexer {
+ friend class master_lexer_internal::State;
+public:
+ /// \brief Exception thrown when we fail to read from the input
+ /// stream or file.
+ class ReadError : public Unexpected {
+ public:
+ ReadError(const char* file, size_t line, const char* what) :
+ Unexpected(file, line, what)
+ {}
+ };
+
+ /// \brief Exception thrown from a wrapper version of
+ /// \c MasterLexer::getNextToken() for non fatal errors.
+ ///
+ /// See the method description for more details.
+ ///
+ /// The \c token_ member variable (read-only) is set to a \c MasterToken
+ /// object of type ERROR indicating the reason for the error.
+ class LexerError : public Exception {
+ public:
+ LexerError(const char* file, size_t line, MasterToken error_token) :
+ Exception(file, line, error_token.getErrorText().c_str()),
+ token_(error_token)
+ {}
+ const MasterToken token_;
+ };
+
+ /// \brief Special value for input source size meaning "unknown".
+ ///
+ /// This constant value will be used as a return value of
+ /// \c getTotalSourceSize() when the size of one of the pushed sources
+ /// is unknown. Note that this value itself is a valid integer in the
+ /// range of the type, so there's still a small possibility of
+ /// ambiguity. In practice, however, the value should be sufficiently
+ /// large that should eliminate the possibility.
+ static const size_t SOURCE_SIZE_UNKNOWN;
+
+ /// \brief Options for getNextToken.
+ ///
+ /// A compound option, indicating multiple options are set, can be
+ /// specified using the logical OR operator (operator|()).
+ enum Options {
+ NONE = 0, ///< No option
+ INITIAL_WS = 1, ///< recognize begin-of-line spaces after an
+ ///< end-of-line
+ QSTRING = 2, ///< recognize quoted string
+ NUMBER = 4 ///< recognize numeric text as integer
+ };
+
+ /// \brief The constructor.
+ ///
+ /// \throw std::bad_alloc Internal resource allocation fails (rare case).
+ MasterLexer();
+
+ /// \brief The destructor.
+ ///
+ /// It internally closes any remaining input sources.
+ ~MasterLexer();
+
+ /// \brief Open a file and make it the current input source of MasterLexer.
+ ///
+ /// The opened file can be explicitly closed by the \c popSource() method;
+ /// if \c popSource() is not called within the lifetime of the
+ /// \c MasterLexer, it will be closed in the destructor.
+ ///
+ /// In the case possible system errors in opening the file (most likely
+ /// because of specifying a non-existent or unreadable file), it returns
+ /// false, and if the optional \c error parameter is non NULL, it will be
+ /// set to a description of the error (any existing content of the string
+ /// will be discarded). If opening the file succeeds, the given
+ /// \c error parameter will be intact.
+ ///
+ /// Note that this method has two styles of error reporting: one by
+ /// returning \c false (and setting \c error optionally) and the other
+ /// by throwing an exception. See the note for the class description
+ /// about the distinction.
+ ///
+ /// \throw InvalidParameter filename is NULL
+ /// \param filename A non NULL string specifying a master file
+ /// \param error If non null, a placeholder to set error description in
+ /// case of failure.
+ ///
+ /// \return true if pushing the file succeeds; false otherwise.
+ bool pushSource(const char* filename, std::string* error = NULL);
+
+ /// \brief Make the given stream the current input source of MasterLexer.
+ ///
+ /// The caller still holds the ownership of the passed stream; it's the
+ /// caller's responsibility to keep it valid as long as it's used in
+ /// \c MasterLexer or to release any resource for the stream after that.
+ /// The caller can explicitly tell \c MasterLexer to stop using the
+ /// stream by calling the \c popSource() method.
+ ///
+ /// The data in \c input must be complete at the time of this call.
+ /// The behavior of the lexer is undefined if the caller builds or adds
+ /// data in \c input after pushing it.
+ ///
+ /// Except for rare case system errors such as memory allocation failure,
+ /// this method is generally expected to be exception free. However,
+ /// it can still throw if it encounters an unexpected failure when it
+ /// tries to identify the "size" of the input source (see
+ /// \c getTotalSourceSize()). It's an unexpected result unless the
+ /// caller intentionally passes a broken stream; otherwise it would mean
+ /// some system-dependent unexpected behavior or possibly an internal bug.
+ /// In these cases it throws an \c Unexpected exception. Note that
+ /// this version of the method doesn't return a boolean unlike the
+ /// other version that takes a file name; since this failure is really
+ /// unexpected and can be critical, it doesn't make sense to give the
+ /// caller an option to continue (other than by explicitly catching the
+ /// exception).
+ ///
+ /// \throw Unexpected An unexpected failure happens in initialization.
+ ///
+ /// \param input An input stream object that produces textual
+ /// representation of DNS RRs.
+ void pushSource(std::istream& input);
+
+ /// \brief Stop using the most recently opened input source (file or
+ /// stream).
+ ///
+ /// If it's a file, the previously opened file will be closed internally.
+ /// If it's a stream, \c MasterLexer will simply stop using
+ /// the stream; the caller can assume it will be never used in
+ /// \c MasterLexer thereafter.
+ ///
+ /// This method must not be called when there is no source pushed for
+ /// \c MasterLexer. This method is otherwise exception free.
+ ///
+ /// \throw isc::InvalidOperation Called with no pushed source.
+ void popSource();
+
+ /// \brief Get number of sources inside the lexer.
+ ///
+ /// This method never throws.
+ size_t getSourceCount() const;
+
+ /// \brief Return the name of the current input source name.
+ ///
+ /// If it's a file, it will be the C string given at the corresponding
+ /// \c pushSource() call, that is, its filename. If it's a stream, it will
+ /// be formatted as \c "stream-%p" where \c %p is hex representation
+ /// of the address of the stream object.
+ ///
+ /// If there is no opened source at the time of the call, this method
+ /// returns an empty string.
+ ///
+ /// \throw std::bad_alloc Resource allocation failed for string
+ /// construction (rare case)
+ ///
+ /// \return A string representation of the current source (see the
+ /// description)
+ std::string getSourceName() const;
+
+ /// \brief Return the input source line number.
+ ///
+ /// If there is an opened source, the return value will be a non-0
+ /// integer indicating the line number of the current source where
+ /// the \c MasterLexer is currently working. The expected usage of
+ /// this value is to print a helpful error message when parsing fails
+ /// by specifically identifying the position of the error.
+ ///
+ /// If there is no opened source at the time of the call, this method
+ /// returns 0.
+ ///
+ /// \throw None
+ ///
+ /// \return The current line number of the source (see the description)
+ size_t getSourceLine() const;
+
+ /// \brief Return the total size of pushed sources.
+ ///
+ /// This method returns the sum of the size of sources that have been
+ /// pushed to the lexer by the time of the call. It would give the
+ /// caller some hint about the amount of data the lexer is working on.
+ ///
+ /// The size of a normal file is equal to the file size at the time of
+ /// the source is pushed. The size of other type of input stream is
+ /// the size of the data available in the stream at the time of the
+ /// source is pushed.
+ ///
+ /// In some special cases, it's possible that the size of the file or
+ /// stream is unknown. It happens, for example, if the standard input
+ /// is associated with a pipe from the output of another process and it's
+ /// specified as an input source. If the size of some of the pushed
+ /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+ ///
+ /// The total size won't change when a source is popped. So the return
+ /// values of this method will monotonically increase or
+ /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN,
+ /// any subsequent call will also result in that value, by the above
+ /// definition.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// \throw None
+ size_t getTotalSourceSize() const;
+
+ /// \brief Return the position of lexer in the pushed sources so far.
+ ///
+ /// This method returns the position in terms of the number of recognized
+ /// characters from all sources that have been pushed by the time of the
+ /// call. Conceptually, the position in a single source is the offset
+ /// from the beginning of the file or stream to the current "read cursor"
+ /// of the lexer. The return value of this method is the sum of the
+ /// positions in all the pushed sources. If any of the sources has
+ /// already been popped, the position of the source at the time of the
+ /// pop operation will be used for the calculation.
+ ///
+ /// If the lexer reaches the end for each of all the pushed sources,
+ /// the return value should be equal to that of \c getTotalSourceSize().
+ /// It's generally expected that a source is popped when the lexer
+ /// reaches the end of the source. So, when the application of this
+ /// class parses all contents of all sources, possibly with multiple
+ /// pushes and pops, the return value of this method and
+ /// \c getTotalSourceSize() should be identical (unless the latter
+ /// returns SOURCE_SIZE_UNKNOWN). But this is not necessarily
+ /// guaranteed as the application can pop a source in the middle of
+ /// parsing it.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// The return values of this method and \c getTotalSourceSize() would
+ /// give the caller an idea of the progress of the lexer at the time of
+ /// the call. Note, however, that since it's not predictable whether
+ /// more sources will be pushed after the call, the progress determined
+ /// this way may not make much sense; it can only give an informational
+ /// hint of the progress.
+ ///
+ /// Note that the conceptual "read cursor" would move backward after a
+ /// call to \c ungetToken(), in which case this method will return a
+ /// smaller value. That is, unlike \c getTotalSourceSize(), return
+ /// values of this method may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
+ /// \brief Parse and return another token from the input.
+ ///
+ /// It reads a bit of the last opened source and produces another token
+ /// found in it.
+ ///
+ /// This method does not provide the strong exception guarantee. Generally,
+ /// if it throws, the object should not be used any more and should be
+ /// discarded. It was decided all the exceptions thrown from here are
+ /// serious enough that aborting the loading process is the only reasonable
+ /// recovery anyway, so the strong exception guarantee is not needed.
+ ///
+ /// \param options The options can be used to modify the tokenization.
+ /// The method can be made reporting things which are usually ignored
+ /// by this parameter. Multiple options can be passed at once by
+ /// bitwise or (eg. option1 | option 2). See description of available
+ /// options.
+ /// \return Next token found in the input. Note that the token refers to
+ /// some internal data in the lexer. It is valid only until
+ /// getNextToken or ungetToken is called. Also, the token becomes
+ /// invalid when the lexer is destroyed.
+ /// \throw isc::InvalidOperation in case the source is not available. This
+ /// may mean the pushSource() has not been called yet, or that the
+ /// current source has been read past the end.
+ /// \throw ReadError in case there's problem reading from the underlying
+ /// source (eg. I/O error in the file on the disk).
+ /// \throw std::bad_alloc in case allocation of some internal resources
+ /// or the token fail.
+ const MasterToken& getNextToken(Options options = NONE);
+
+ /// \brief Parse the input for the expected type of token.
+ ///
+ /// This method is a wrapper of the other version, customized for the case
+ /// where a particular type of token is expected as the next one.
+ /// More specifically, it's intended to be used to get tokens for RDATA
+ /// fields. Since most RDATA types of fixed format, the token type is
+ /// often predictable and the method interface can be simplified.
+ ///
+ /// This method basically works as follows: it gets the type of the
+ /// expected token, calls the other version of \c getNextToken(Options),
+ /// and returns the token if it's of the expected type (due to the usage
+ /// assumption this should be normally the case). There are some non
+ /// trivial details though:
+ ///
+ /// - If the expected type is MasterToken::QSTRING, both quoted and
+ /// unquoted strings are recognized and returned.
+ /// - If the optional \c eol_ok parameter is \c true (very rare case),
+ /// MasterToken::END_OF_LINE and MasterToken::END_OF_FILE are recognized
+ /// and returned if they are found instead of the expected type of
+ /// token.
+ /// - If the next token is not of the expected type (including the case
+ /// a number is expected but it's out of range), ungetToken() is
+ /// internally called so the caller can re-read that token.
+ /// - If other types or errors (such as unbalanced parentheses) are
+ /// detected, the erroneous part isn't "ungotten"; the caller can
+ /// continue parsing after that part.
+ ///
+ /// In some very rare cases where the RDATA has an optional trailing field,
+ /// the \c eol_ok parameter would be set to \c true. This way the caller
+ /// can handle both cases (the field does or does not exist) by a single
+ /// call to this method. In all other cases \c eol_ok should be set to
+ /// \c false, and that is the default and can be omitted.
+ ///
+ /// Unlike the other version of \c getNextToken(Options), this method
+ /// throws an exception of type \c LexerError for non fatal errors such as
+ /// broken syntax or encountering an unexpected type of token. This way
+ /// the caller can write RDATA parser code without bothering to handle
+ /// errors for each field. For example, pseudo parser code for MX RDATA
+ /// would look like this:
+ /// \code
+ /// const uint32_t pref =
+ /// lexer.getNextToken(MasterToken::NUMBER).getNumber();
+ /// // check if pref is the uint16_t range; no other check is needed.
+ /// const Name mx(lexer.getNextToken(MasterToken::STRING).getString());
+ /// \endcode
+ ///
+ /// In the case where \c LexerError exception is thrown, it's expected
+ /// to be handled comprehensively for the parser of the RDATA or at a
+ /// higher layer. The \c token_ member variable of the corresponding
+ /// \c LexerError exception object stores a token of type
+ /// \c MasterToken::ERROR that indicates the reason for the error.
+ ///
+ /// Due to the specific intended usage of this method, only a subset
+ /// of \c MasterToken::Type values are acceptable for the \c expect
+ /// parameter: \c MasterToken::STRING, \c MasterToken::QSTRING, and
+ /// \c MasterToken::NUMBER. Specifying other values will result in
+ /// an \c InvalidParameter exception.
+ ///
+ /// \throw InvalidParameter The expected token type is not allowed for
+ /// this method.
+ /// \throw LexerError The lexer finds non fatal error or it finds an
+ /// \throw other Anything the other version of getNextToken() can throw.
+ ///
+ /// \param expect Expected type of token. Must be either STRING, QSTRING,
+ /// or NUMBER.
+ /// \param eol_ok \c true iff END_OF_LINE or END_OF_FILE is acceptable.
+ /// \return The expected type of token.
+ const MasterToken& getNextToken(MasterToken::Type expect,
+ bool eol_ok = false);
+
+ /// \brief Return the last token back to the lexer.
+ ///
+ /// The method undoes the lasts call to getNextToken(). If you call the
+ /// getNextToken() again with the same options, it'll return the same
+ /// token. If the options are different, it may return a different token,
+ /// but it acts as if the previous getNextToken() was never called.
+ ///
+ /// It is possible to return only one token back in time (you can't call
+ /// ungetToken() twice in a row without calling getNextToken() in between
+ /// successfully).
+ ///
+ /// It does not work after change of source (by pushSource or popSource).
+ ///
+ /// \throw isc::InvalidOperation If called second time in a row or if
+ /// getNextToken() was not called since the last change of the source.
+ void ungetToken();
+
+private:
+ struct MasterLexerImpl;
+ MasterLexerImpl* impl_;
+};
+
+/// \brief Operator to combine \c MasterLexer options
+///
+/// This is a trivial shortcut so that compound options can be specified
+/// in an intuitive way.
+inline MasterLexer::Options
+operator|(MasterLexer::Options o1, MasterLexer::Options o2) {
+ return (static_cast<MasterLexer::Options>(
+ static_cast<unsigned>(o1) | static_cast<unsigned>(o2)));
+}
+
} // namespace dns
} // namespace isc
#endif // MASTER_LEXER_H
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
index effe163..cc1f505 100644
--- a/src/lib/dns/master_lexer_inputsource.cc
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -13,7 +13,11 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
+#include <istream>
+#include <iostream>
+#include <cassert>
#include <cerrno>
#include <cstring>
@@ -30,6 +34,54 @@ createStreamName(const std::istream& input_stream) {
return (ss.str());
}
+size_t
+getStreamSize(std::istream& is) {
+ errno = 0; // see below
+ is.seekg(0, std::ios_base::end);
+ if (is.bad()) {
+ // This means the istream has an integrity error. It doesn't make
+ // sense to continue from this point, so we treat it as a fatal error.
+ isc_throw(InputSource::OpenError,
+ "failed to seek end of input source");
+ } else if (is.fail() || errno != 0) {
+ // This is an error specific to seekg(). There can be several
+ // reasons, but the most likely cause in this context is that the
+ // stream is associated with a special type of file such as a pipe.
+ // In this case, it's more likely that other main operations of
+ // the input source work fine, so we continue with just setting
+ // the stream size to "unknown".
+ //
+ // (At least some versions of) Solaris + SunStudio shows deviant
+ // behavior here: seekg() apparently calls lseek(2) internally, but
+ // even if it fails it doesn't set the error bits of istream. That will
+ // confuse the rest of this function, so, as a heuristic workaround
+ // we check errno and handle any non 0 value as fail().
+ is.clear(); // clear this error not to confuse later ops.
+ return (MasterLexer::SOURCE_SIZE_UNKNOWN);
+ }
+ const std::streampos len = is.tellg();
+ size_t ret = len;
+ if (len == static_cast<std::streampos>(-1)) { // cast for some compilers
+ if (!is.fail()) {
+ // tellg() returns -1 if istream::fail() would be true, but it's
+ // not guaranteed that it shouldn't be returned in other cases.
+ // In fact, with the combination of SunStudio and stlport,
+ // a stringstream created by the default constructor showed that
+ // behavior. We treat such cases as an unknown size.
+ ret = MasterLexer::SOURCE_SIZE_UNKNOWN;
+ } else {
+ isc_throw(InputSource::OpenError, "failed to get input size");
+ }
+ }
+ is.seekg(0, std::ios::beg);
+ if (is.fail()) {
+ isc_throw(InputSource::OpenError,
+ "failed to seek beginning of input source");
+ }
+ assert(len >= 0 || ret == MasterLexer::SOURCE_SIZE_UNKNOWN);
+ return (ret);
+}
+
} // end of unnamed namespace
// Explicit definition of class static constant. The value is given in the
@@ -41,31 +93,44 @@ InputSource::InputSource(std::istream& input_stream) :
line_(1),
saved_line_(line_),
buffer_pos_(0),
+ total_pos_(0),
name_(createStreamName(input_stream)),
- input_(input_stream)
+ input_(input_stream),
+ input_size_(getStreamSize(input_))
{}
-InputSource::InputSource(const char* filename) :
- at_eof_(false),
- line_(1),
- saved_line_(line_),
- buffer_pos_(0),
- name_(filename),
- input_(file_stream_)
-{
+namespace {
+// A helper to initialize InputSource::input_ in the member initialization
+// list.
+std::istream&
+openFileStream(std::ifstream& file_stream, const char* filename) {
errno = 0;
- file_stream_.open(filename);
- if (file_stream_.fail()) {
+ file_stream.open(filename);
+ if (file_stream.fail()) {
std::string error_txt("Error opening the input source file: ");
error_txt += filename;
if (errno != 0) {
error_txt += "; possible cause: ";
error_txt += std::strerror(errno);
}
- isc_throw(OpenError, error_txt);
+ isc_throw(InputSource::OpenError, error_txt);
}
+
+ return (file_stream);
+}
}
+InputSource::InputSource(const char* filename) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ total_pos_(0),
+ name_(filename),
+ input_(openFileStream(file_stream_, filename)),
+ input_size_(getStreamSize(input_))
+{}
+
InputSource::~InputSource()
{
if (file_stream_.is_open()) {
@@ -94,7 +159,7 @@ InputSource::getChar() {
// This has to come after the .eof() check as some
// implementations seem to check the eofbit also in .fail().
if (input_.fail()) {
- isc_throw(ReadError,
+ isc_throw(MasterLexer::ReadError,
"Error reading from the input stream: " << getName());
}
buffer_.push_back(c);
@@ -102,6 +167,7 @@ InputSource::getChar() {
const int c = buffer_[buffer_pos_];
++buffer_pos_;
+ ++total_pos_;
if (c == '\n') {
++line_;
}
@@ -118,6 +184,7 @@ InputSource::ungetChar() {
"Cannot skip before the start of buffer");
} else {
--buffer_pos_;
+ --total_pos_;
if (buffer_[buffer_pos_] == '\n') {
--line_;
}
@@ -126,6 +193,8 @@ InputSource::ungetChar() {
void
InputSource::ungetAll() {
+ assert(total_pos_ >= buffer_pos_);
+ total_pos_ -= buffer_pos_;
buffer_pos_ = 0;
line_ = saved_line_;
at_eof_ = false;
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
index 8feffa2..2ad0100 100644
--- a/src/lib/dns/master_lexer_inputsource.h
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -56,14 +56,6 @@ public:
{}
};
- /// \brief Exception thrown when we fail to read from the input
- /// stream or file.
- struct ReadError : public Unexpected {
- ReadError(const char* file, size_t line, const char* what) :
- Unexpected(file, line, what)
- {}
- };
-
/// \brief Exception thrown when we fail to open the input file.
struct OpenError : public Unexpected {
OpenError(const char* file, size_t line, const char* what) :
@@ -73,12 +65,16 @@ public:
/// \brief Constructor which takes an input stream. The stream is
/// read-from, but it is not closed.
+ ///
+ /// \throws OpenError If the data size of the input stream cannot be
+ /// detected.
explicit InputSource(std::istream& input_stream);
/// \brief Constructor which takes a filename to read from. The
/// associated file stream is managed internally.
///
- /// \throws OpenError when opening the input file fails.
+ /// \throws OpenError when opening the input file fails or the size of
+ /// the file cannot be detected.
explicit InputSource(const char* filename);
/// \brief Destructor
@@ -91,6 +87,34 @@ public:
return (name_);
}
+ /// \brief Returns the size of the input source in bytes.
+ ///
+ /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// See \c MasterLexer::getTotalSourceSize() for the definition of
+ /// the size of sources and for when the size can be unknown.
+ ///
+ /// \throw None
+ size_t getSize() const { return (input_size_); }
+
+ /// \brief Returns the current read position in the input source.
+ ///
+ /// This method returns the position of the character that was last
+ /// retrieved from the source. Unless some characters have been
+ /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal
+ /// to the number of calls to \c getChar() until it reaches the
+ /// END_OF_STREAM. Note that the position of the first character in
+ /// the source is 1. At the point of the last character, the return value
+ /// of this method should be equal to that of \c getSize(), and
+ /// recognizing END_OF_STREAM doesn't increase the position.
+ ///
+ /// If \c ungetChar() or \c ungetAll() is called, the position is
+ /// decreased by the number of "ungotten" characters. So the return
+ /// values may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const { return (total_pos_); }
+
/// \brief Returns if the input source is at end of file.
bool atEOF() const {
return (at_eof_);
@@ -124,8 +148,8 @@ public:
/// \brief Returns a single character from the input source. If end
/// of file is reached, \c END_OF_STREAM is returned.
///
- /// \throws ReadError when reading from the input stream or file
- /// fails.
+ /// \throws MasterLexer::ReadError when reading from the input stream or
+ /// file fails.
int getChar();
/// \brief Skips backward a single character in the input
@@ -150,10 +174,12 @@ private:
std::vector<char> buffer_;
size_t buffer_pos_;
+ size_t total_pos_;
const std::string name_;
std::ifstream file_stream_;
std::istream& input_;
+ const size_t input_size_;
};
} // namespace master_lexer_internal
diff --git a/src/lib/dns/master_lexer_state.h b/src/lib/dns/master_lexer_state.h
index 1130f33..f296c1c 100644
--- a/src/lib/dns/master_lexer_state.h
+++ b/src/lib/dns/master_lexer_state.h
@@ -17,6 +17,8 @@
#include <dns/master_lexer.h>
+#include <boost/function.hpp>
+
namespace isc {
namespace dns {
@@ -41,10 +43,10 @@ namespace master_lexer_internal {
/// state, so it makes more sense to separate the interface for the transition
/// from the initial state.
///
-/// When an object of a specific state class completes the session, it
-/// normally sets the identified token in the lexer, and returns NULL;
-/// if more transition is necessary, it returns a pointer to the next state
-/// object.
+/// If the whole lexer transition is completed within start(), it sets the
+/// identified token and returns NULL; otherwise it returns a pointer to
+/// an object of a specific state class that completes the session
+/// on the call of handle().
///
/// As is usual in the state design pattern, the \c State class is made
/// a friend class of \c MasterLexer and can refer to its internal details.
@@ -67,7 +69,7 @@ public:
/// tokenization session. The lexer passes a reference to itself
/// and options given in \c getNextToken().
///
- /// \throw InputSource::ReadError Unexpected I/O error
+ /// \throw MasterLexer::ReadError Unexpected I/O error
/// \throw std::bad_alloc Internal resource allocation failure
///
/// \param lexer The lexer object that holds the main context.
@@ -80,16 +82,16 @@ public:
/// \brief Handle the process of one specific state.
///
/// This method is expected to be called on the object returned by
- /// start(), and keep called on the returned object until NULL is
- /// returned. The call chain will form the complete state transition.
+ /// start(). In the usual state transition design pattern, it would
+ /// return the next state. But as we noticed, we never have another
+ /// state, so we simplify it by not returning anything instead of
+ /// returning NULL every time.
///
- /// \throw InputSource::ReadError Unexpected I/O error
+ /// \throw MasterLexer::ReadError Unexpected I/O error
/// \throw std::bad_alloc Internal resource allocation failure
///
/// \param lexer The lexer object that holds the main context.
- /// \return A pointer to the next state object or NULL if the transition
- /// is completed.
- virtual const State* handle(MasterLexer& lexer) const = 0;
+ virtual void handle(MasterLexer& lexer) const = 0;
/// \brief Types of states.
///
@@ -98,7 +100,9 @@ public:
/// a way to get an instance of a specific state.
enum ID {
CRLF, ///< Just seen a carriage-return character
- String ///< Handling a string token
+ String, ///< Handling a string token
+ QString, ///< Handling a quoted string token
+ Number ///< Handling a number
};
/// \brief Returns a \c State instance of the given state.
@@ -115,7 +119,7 @@ public:
/// purposes.
///@{
bool wasLastEOL(const MasterLexer& lexer) const;
- const MasterLexer::Token& getToken(const MasterLexer& lexer) const;
+ const MasterToken& getToken(const MasterLexer& lexer) const;
size_t getParenCount(const MasterLexer& lexer) const;
///@}
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
new file mode 100644
index 0000000..29e7e1d
--- /dev/null
+++ b/src/lib/dns/master_loader.cc
@@ -0,0 +1,651 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_loader.h>
+#include <dns/master_lexer.h>
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/algorithm/string/predicate.hpp> // for iequals
+
+#include <string>
+#include <memory>
+#include <vector>
+#include <boost/algorithm/string/predicate.hpp> // for iequals
+#include <boost/shared_ptr.hpp>
+
+using std::string;
+using std::auto_ptr;
+using std::vector;
+using std::pair;
+using boost::algorithm::iequals;
+using boost::shared_ptr;
+
+namespace isc {
+namespace dns {
+
+namespace {
+
+// An internal exception, used to control the code flow in case of errors.
+// It is thrown during the loading and caught later, not to be propagated
+// outside of the file.
+class InternalException : public isc::Exception {
+public:
+ InternalException(const char* filename, size_t line, const char* what) :
+ Exception(filename, line, what)
+ {}
+};
+
+} // end unnamed namespace
+
+class MasterLoader::MasterLoaderImpl {
+public:
+ MasterLoaderImpl(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ MasterLoader::Options options) :
+ lexer_(),
+ zone_origin_(zone_origin),
+ active_origin_(zone_origin),
+ zone_class_(zone_class),
+ callbacks_(callbacks),
+ add_callback_(add_callback),
+ options_(options),
+ master_file_(master_file),
+ initialized_(false),
+ ok_(true),
+ many_errors_((options & MANY_ERRORS) != 0),
+ previous_name_(false),
+ complete_(false),
+ seen_error_(false),
+ warn_rfc1035_ttl_(true),
+ rr_count_(0)
+ {}
+
+ void pushSource(const std::string& filename, const Name& current_origin) {
+ std::string error;
+ if (!lexer_.pushSource(filename.c_str(), &error)) {
+ if (initialized_) {
+ isc_throw(InternalException, error.c_str());
+ } else {
+ // Top-level file
+ reportError("", 0, error);
+ ok_ = false;
+ }
+ }
+ // Store the current status, so we can recover it upon popSource
+ include_info_.push_back(IncludeInfo(current_origin, last_name_));
+ initialized_ = true;
+ previous_name_ = false;
+ }
+
+ void pushStreamSource(std::istream& stream) {
+ lexer_.pushSource(stream);
+ initialized_ = true;
+ }
+
+ bool loadIncremental(size_t count_limit);
+
+ size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+ size_t getPosition() const { return (lexer_.getPosition()); }
+
+private:
+ void reportError(const std::string& filename, size_t line,
+ const std::string& reason)
+ {
+ seen_error_ = true;
+ callbacks_.error(filename, line, reason);
+ if (!many_errors_) {
+ // In case we don't have the lenient mode, every error is fatal
+ // and we throw
+ ok_ = false;
+ complete_ = true;
+ isc_throw(MasterLoaderError, reason.c_str());
+ }
+ }
+
+ bool popSource() {
+ if (lexer_.getSourceCount() == 1) {
+ return (false);
+ }
+ lexer_.popSource();
+ // Restore original origin and last seen name
+
+ // We move in tandem, there's an extra item included during the
+ // initialization, so we can never run out of them
+ assert(!include_info_.empty());
+ const IncludeInfo& info(include_info_.back());
+ active_origin_ = info.first;
+ last_name_ = info.second;
+ include_info_.pop_back();
+ previous_name_ = false;
+ return (true);
+ }
+
+ // Get a string token. Handle it as error if it is not string.
+ const string getString() {
+ lexer_.getNextToken(MasterToken::STRING).getString(string_token_);
+ return (string_token_);
+ }
+
+ MasterToken handleInitialToken();
+
+ void doOrigin(bool is_optional) {
+ // Parse and create the new origin. It is relative to the previous
+ // one.
+ const MasterToken&
+ name_tok(lexer_.getNextToken(MasterToken::QSTRING, is_optional));
+
+ if (name_tok.getType() == MasterToken::QSTRING ||
+ name_tok.getType() == MasterToken::STRING) {
+
+ const MasterToken::StringRegion&
+ name_string(name_tok.getStringRegion());
+ active_origin_ = Name(name_string.beg, name_string.len,
+ &active_origin_);
+ if (name_string.len > 0 &&
+ name_string.beg[name_string.len - 1] != '.') {
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "The new origin is relative, did you really"
+ " mean " + active_origin_.toText() + "?");
+ }
+ } else {
+ // If it is not optional, we must not get anything but
+ // a string token.
+ assert(is_optional);
+
+ // We return the newline there. This is because we want to
+ // behave the same if there is or isn't the name, leaving the
+ // newline there.
+ lexer_.ungetToken();
+ }
+ }
+
+ void doInclude() {
+ // First, get the filename to include
+ const string
+ filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
+
+ // There optionally can be an origin, that applies before the include.
+ // We need to save the currently active origin before calling
+ // doOrigin(), because it would update active_origin_ while we need
+ // to pass the active origin before recognizing the new origin to
+ // pushSource. Note: RFC 1035 is not really clear on this: it reads
+ // "regardless of changes... within the included file", but the new
+ // origin is not really specified "within the included file".
+ // Nevertheless, this behavior is probably more likely to be the
+ // intent of the RFC, and it's compatible with BIND 9.
+ const Name current_origin = active_origin_;
+ doOrigin(true);
+
+ pushSource(filename, current_origin);
+ }
+
+ // A helper method for loadIncremental(). It parses part of an RR
+ // until it finds the RR type field. If TTL or RR class is
+ // specified before the RR type, it also recognizes and validates
+ // them. explicit_ttl will be set to true if this method finds a
+ // valid TTL field.
+ RRType parseRRParams(bool& explicit_ttl, MasterToken rrparam_token) {
+ // Find TTL, class and type. Both TTL and class are
+ // optional and may occur in any order if they exist. TTL
+ // and class come before type which must exist.
+ //
+ // [<TTL>] [<class>] <type> <RDATA>
+ // [<class>] [<TTL>] <type> <RDATA>
+
+ // named-signzone outputs TTL first, so try parsing it in order
+ // first.
+ if (setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ } else {
+ // If it's not a TTL here, continue and try again
+ // after the RR class below.
+ }
+
+ const MaybeRRClass rrclass =
+ RRClass::createFromText(rrparam_token.getString());
+ if (rrclass) {
+ // FIXME: The following code re-parses the rrparam_token to
+ // make an RRClass instead of using the MaybeRRClass above,
+ // because some old versions of boost::optional (that we
+ // still want to support) have a bug (see trac #2593). This
+ // workaround should be removed at some point in the future.
+ if (RRClass(rrparam_token.getString()) != zone_class_) {
+ isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+ " vs. " << zone_class_);
+ }
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // If we couldn't parse TTL earlier in the stream (above), try
+ // again at current location.
+ if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
+ explicit_ttl = true;
+ rrparam_token = lexer_.getNextToken(MasterToken::STRING);
+ }
+
+ // Return the current string token's value as the RRType.
+ return (RRType(rrparam_token.getString()));
+ }
+
+ // Upper limit check when recognizing a specific TTL value from the
+ // zone file ($TTL, the RR's TTL field, or the SOA minimum). RFC2181
+ // Section 8 limits the range of TTL values to 2^31-1 (0x7fffffff),
+ // and prohibits transmitting a TTL field exceeding this range. We
+ // guarantee that by limiting the value at the time of zone
+ // parsing/loading, following what BIND 9 does. Resetting it to 0
+ // at this point may not be exactly what the RFC states (depending on
+ // the meaning of 'received'), but the end result would be the same (i.e.,
+ // the guarantee on transmission). Again, we follow the BIND 9's behavior
+ // here.
+ //
+ // post_parsing is true iff this method is called after parsing the entire
+ // RR and the lexer is positioned at the next line. It's just for
+ // calculating the accurate source line when callback is necessary.
+ void limitTTL(RRTTL& ttl, bool post_parsing) {
+ if (ttl > RRTTL::MAX_TTL()) {
+ const size_t src_line = lexer_.getSourceLine() -
+ (post_parsing ? 1 : 0);
+ callbacks_.warning(lexer_.getSourceName(), src_line,
+ "TTL " + ttl.toText() + " > MAXTTL, "
+ "setting to 0 per RFC2181");
+ ttl = RRTTL(0);
+ }
+ }
+
+ // Set/reset the default TTL. This should be from either $TTL or SOA
+ // minimum TTL (it's the caller's responsibility; this method doesn't
+ // care about where it comes from). see LimitTTL() for parameter
+ // post_parsing.
+ void setDefaultTTL(const RRTTL& ttl, bool post_parsing) {
+ if (!default_ttl_) {
+ default_ttl_.reset(new RRTTL(ttl));
+ } else {
+ *default_ttl_ = ttl;
+ }
+ limitTTL(*default_ttl_, post_parsing);
+ }
+
+ // Try to set/reset the current TTL from candidate TTL text. It's possible
+ // it does not actually represent a TTL (which is not immediately
+ // considered an error). Return true iff it's recognized as a valid TTL
+ // (and only in which case the current TTL is set).
+ bool setCurrentTTL(const string& ttl_txt) {
+ // We use the factory version instead of RRTTL constructor as we
+ // need to expect cases where ttl_txt does not actually represent a TTL
+ // but an RR class or type.
+ const MaybeRRTTL maybe_ttl = RRTTL::createFromText(ttl_txt);
+ if (maybe_ttl) {
+ current_ttl_ = maybe_ttl;
+ limitTTL(*current_ttl_, false);
+ return (true);
+ }
+ return (false);
+ }
+
+ // Determine the TTL of the current RR based on the given parsing context.
+ //
+ // explicit_ttl is true iff the TTL is explicitly specified for that RR
+ // (in which case current_ttl_ is set to that TTL).
+ // rrtype is the type of the current RR, and rdata is its RDATA. They
+ // only matter if the type is SOA and no available TTL is known. In this
+ // case the minimum TTL of the SOA will be used as the TTL of that SOA
+ // and the default TTL for subsequent RRs.
+ const RRTTL& getCurrentTTL(bool explicit_ttl, const RRType& rrtype,
+ const rdata::ConstRdataPtr& rdata) {
+ // We've completed parsing the full of RR, and the lexer is already
+ // positioned at the next line. If we need to call callback,
+ // we need to adjust the line number.
+ const size_t current_line = lexer_.getSourceLine() - 1;
+
+ if (!current_ttl_ && !default_ttl_) {
+ if (rrtype == RRType::SOA()) {
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "no TTL specified; "
+ "using SOA MINTTL instead");
+ const uint32_t ttl_val =
+ dynamic_cast<const rdata::generic::SOA&>(*rdata).
+ getMinimum();
+ setDefaultTTL(RRTTL(ttl_val), true);
+ current_ttl_ = *default_ttl_;
+ } else {
+ // On catching the exception we'll try to reach EOL again,
+ // so we need to unget it now.
+ lexer_.ungetToken();
+ throw InternalException(__FILE__, __LINE__,
+ "no TTL specified; load rejected");
+ }
+ } else if (!explicit_ttl && default_ttl_) {
+ current_ttl_ = *default_ttl_;
+ } else if (!explicit_ttl && warn_rfc1035_ttl_) {
+ // Omitted (class and) TTL values are default to the last
+ // explicitly stated values (RFC 1035, Sec. 5.1).
+ callbacks_.warning(lexer_.getSourceName(), current_line,
+ "using RFC1035 TTL semantics; default to the "
+ "last explicitly stated TTL");
+ warn_rfc1035_ttl_ = false; // we only warn about this once
+ }
+ assert(current_ttl_);
+ return (*current_ttl_);
+ }
+
+ void handleDirective(const char* directive, size_t length) {
+ if (iequals(directive, "INCLUDE")) {
+ doInclude();
+ } else if (iequals(directive, "ORIGIN")) {
+ doOrigin(false);
+ eatUntilEOL(true);
+ } else if (iequals(directive, "TTL")) {
+ setDefaultTTL(RRTTL(getString()), false);
+ eatUntilEOL(true);
+ } else {
+ isc_throw(InternalException, "Unknown directive '" <<
+ string(directive, directive + length) << "'");
+ }
+ }
+
+ void eatUntilEOL(bool reportExtra) {
+ // We want to continue. Try to read until the end of line
+ for (;;) {
+ const MasterToken& token(lexer_.getNextToken());
+ switch (token.getType()) {
+ case MasterToken::END_OF_FILE:
+ callbacks_.warning(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "File does not end with newline");
+ // We don't pop here. The End of file will stay there,
+ // and we'll handle it in the next iteration of
+ // loadIncremental properly.
+ return;
+ case MasterToken::END_OF_LINE:
+ // Found the end of the line. Good.
+ return;
+ default:
+ // Some other type of token.
+ if (reportExtra) {
+ reportExtra = false;
+ reportError(lexer_.getSourceName(),
+ lexer_.getSourceLine(),
+ "Extra tokens at the end of line");
+ }
+ break;
+ }
+ }
+ }
+
+private:
+ MasterLexer lexer_;
+ const Name zone_origin_;
+ Name active_origin_; // The origin used during parsing
+ // (modifiable by $ORIGIN)
+ shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
+ const RRClass zone_class_;
+ MasterLoaderCallbacks callbacks_;
+ const AddRRCallback add_callback_;
+ boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
+ // unspecified. If NULL no default
+ // is known.
+ MaybeRRTTL current_ttl_; // The TTL used most recently. Initially unset.
+ // Once set always stores a valid RRTTL.
+ const MasterLoader::Options options_;
+ const std::string master_file_;
+ std::string string_token_;
+ bool initialized_;
+ bool ok_; // Is it OK to continue loading?
+ const bool many_errors_; // Are many errors allowed (or should we abort
+ // on the first)
+ // Some info about the outer files from which we include.
+ // The first one is current origin, the second is the last seen name
+ // in that file.
+ typedef pair<Name, shared_ptr<Name> > IncludeInfo;
+ vector<IncludeInfo> include_info_;
+ bool previous_name_; // True if there was a previous name in this file
+ // (false at the beginning or after an $INCLUDE line)
+
+public:
+ bool complete_; // All work done.
+ bool seen_error_; // Was there at least one error during the
+ // load?
+ bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
+ // from the previous RR is used.
+ size_t rr_count_; // number of RRs successfully loaded
+};
+
+// A helper method of loadIncremental, parsing the first token of a new line.
+// If it looks like an RR, detect its owner name and return a string token for
+// the next field of the RR.
+// Otherwise, return either END_OF_LINE or END_OF_FILE token depending on
+// whether the loader continues to the next line or completes the load,
+// respectively. Other corner cases including $-directive handling is done
+// here.
+// For unexpected errors, it throws an exception, which will be handled in
+// loadIncremental.
+MasterToken
+MasterLoader::MasterLoaderImpl::handleInitialToken() {
+ const MasterToken& initial_token =
+ lexer_.getNextToken(MasterLexer::QSTRING | MasterLexer::INITIAL_WS);
+
+ // The most likely case is INITIAL_WS, and then string/qstring. We
+ // handle them first.
+ if (initial_token.getType() == MasterToken::INITIAL_WS) {
+ const MasterToken& next_token = lexer_.getNextToken();
+ if (next_token.getType() == MasterToken::END_OF_LINE) {
+ return (next_token); // blank line
+ } else if (next_token.getType() == MasterToken::END_OF_FILE) {
+ lexer_.ungetToken(); // handle it in the next iteration.
+ eatUntilEOL(true); // effectively warn about the unexpected EOF.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This means the same name as previous.
+ if (last_name_.get() == NULL) {
+ isc_throw(InternalException, "No previous name to use in "
+ "place of initial whitespace");
+ } else if (!previous_name_) {
+ callbacks_.warning(lexer_.getSourceName(), lexer_.getSourceLine(),
+ "Owner name omitted around $INCLUDE, the result "
+ "might not be as expected");
+ }
+ return (next_token);
+ } else if (initial_token.getType() == MasterToken::STRING ||
+ initial_token.getType() == MasterToken::QSTRING) {
+ // If it is name (or directive), handle it.
+ const MasterToken::StringRegion&
+ name_string(initial_token.getStringRegion());
+
+ if (name_string.len > 0 && name_string.beg[0] == '$') {
+ // This should have either thrown (and the error handler
+ // will read up until the end of line) or read until the
+ // end of line.
+
+ // Exclude the $ from the string on this point.
+ handleDirective(name_string.beg + 1, name_string.len - 1);
+ // So, get to the next line, there's nothing more interesting
+ // in this one.
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+
+ // This should be an RR, starting with an owner name. Construct the
+ // name, and some string token should follow.
+ last_name_.reset(new Name(name_string.beg, name_string.len,
+ &active_origin_));
+ previous_name_ = true;
+ return (lexer_.getNextToken(MasterToken::STRING));
+ }
+
+ switch (initial_token.getType()) { // handle less common cases
+ case MasterToken::END_OF_FILE:
+ if (!popSource()) {
+ return (initial_token);
+ } else {
+ // We try to read a token from the popped source
+ // So continue to the next line of that source, but first, make
+ // sure the source is at EOL
+ eatUntilEOL(true);
+ return (MasterToken(MasterToken::END_OF_LINE));
+ }
+ case MasterToken::END_OF_LINE:
+ return (initial_token); // empty line
+ case MasterToken::ERROR:
+ // Error token here.
+ isc_throw(InternalException, initial_token.getErrorText());
+ default:
+ // Some other token (what could that be?)
+ isc_throw(InternalException, "Parser got confused (unexpected "
+ "token " << initial_token.getType() << ")");
+ }
+}
+
+bool
+MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
+ if (count_limit == 0) {
+ isc_throw(isc::InvalidParameter, "Count limit set to 0");
+ }
+ if (complete_) {
+ isc_throw(isc::InvalidOperation,
+ "Trying to load when already loaded");
+ }
+ if (!initialized_) {
+ pushSource(master_file_, active_origin_);
+ }
+ size_t count = 0;
+ while (ok_ && count < count_limit) {
+ try {
+ const MasterToken next_token = handleInitialToken();
+ if (next_token.getType() == MasterToken::END_OF_FILE) {
+ return (true); // we are done
+ } else if (next_token.getType() == MasterToken::END_OF_LINE) {
+ continue; // nothing more to do in this line
+ }
+ // We are going to parse an RR, have known the owner name,
+ // and are now seeing the next string token in the rest of the RR.
+ assert(next_token.getType() == MasterToken::STRING);
+
+ bool explicit_ttl = false;
+ const RRType rrtype = parseRRParams(explicit_ttl, next_token);
+ // TODO: Check if it is SOA, it should be at the origin.
+
+ const rdata::RdataPtr rdata =
+ rdata::createRdata(rrtype, zone_class_, lexer_,
+ &active_origin_, options_, callbacks_);
+
+ // In case we get NULL, it means there was error creating
+ // the Rdata. The errors should have been reported by
+ // callbacks_ already. We need to decide if we want to continue
+ // or not.
+ if (rdata) {
+ add_callback_(*last_name_, zone_class_, rrtype,
+ getCurrentTTL(explicit_ttl, rrtype, rdata),
+ rdata);
+ // Good, we loaded another one
+ ++count;
+ ++rr_count_;
+ } else {
+ seen_error_ = true;
+ if (!many_errors_) {
+ ok_ = false;
+ complete_ = true;
+ // We don't have the exact error here, but it was reported
+ // by the error callback.
+ isc_throw(MasterLoaderError, "Invalid RR data");
+ }
+ }
+ } catch (const MasterLoaderError&) {
+ // This is a hack. We exclude the MasterLoaderError from the
+ // below case. Once we restrict the below to some smaller
+ // exception, we should remove this.
+ throw;
+ } catch (const isc::Exception& e) {
+ // TODO: Once we do #2518, catch only the DNSTextError here,
+ // not isc::Exception. The rest should be just simply
+ // propagated.
+ reportError(lexer_.getSourceName(), lexer_.getSourceLine(),
+ e.what());
+ eatUntilEOL(false);
+ }
+ }
+ // When there was a fatal error and ok is false, we say we are done.
+ return (!ok_);
+}
+
+MasterLoader::MasterLoader(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options)
+{
+ if (add_callback.empty()) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ impl_ = new MasterLoaderImpl(master_file, zone_origin,
+ zone_class, callbacks, add_callback, options);
+}
+
+MasterLoader::MasterLoader(std::istream& stream,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options)
+{
+ if (add_callback.empty()) {
+ isc_throw(isc::InvalidParameter, "Empty add RR callback");
+ }
+ auto_ptr<MasterLoaderImpl> impl(new MasterLoaderImpl("", zone_origin,
+ zone_class, callbacks,
+ add_callback,
+ options));
+ impl->pushStreamSource(stream);
+ impl_ = impl.release();
+}
+
+MasterLoader::~MasterLoader() {
+ delete impl_;
+}
+
+bool
+MasterLoader::loadIncremental(size_t count_limit) {
+ const bool result = impl_->loadIncremental(count_limit);
+ impl_->complete_ = result;
+ return (result);
+}
+
+bool
+MasterLoader::loadedSucessfully() const {
+ return (impl_->complete_ && !impl_->seen_error_);
+}
+
+size_t
+MasterLoader::getSize() const {
+ return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+ return (impl_->getPosition());
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
new file mode 100644
index 0000000..37b2ef4
--- /dev/null
+++ b/src/lib/dns/master_loader.h
@@ -0,0 +1,195 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MASTER_LOADER_H
+#define MASTER_LOADER_H
+
+#include <dns/master_loader_callbacks.h>
+
+#include <boost/noncopyable.hpp>
+
+namespace isc {
+namespace dns {
+
+class Name;
+class RRClass;
+
+/// \brief Error while loading by MasterLoader without specifying the
+/// MANY_ERRORS option.
+class MasterLoaderError : public isc::Exception {
+public:
+ MasterLoaderError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief A class able to load DNS master files
+///
+/// This class is able to produce a stream of RRs from a master file.
+/// It is able to load all of the master file at once, or by blocks
+/// incrementally.
+///
+/// It reports the loaded RRs and encountered errors by callbacks.
+class MasterLoader : boost::noncopyable {
+public:
+ /// \brief Options how the parsing should work.
+ enum Options {
+ DEFAULT = 0, ///< Nothing special.
+ MANY_ERRORS = 1 ///< Lenient mode (see documentation of MasterLoader
+ /// constructor).
+ };
+
+ /// \brief Constructor
+ ///
+ /// This creates a master loader and provides it with all
+ /// relevant information.
+ ///
+ /// Except for the exceptions listed below, the constructor doesn't
+ /// throw. Most errors (like non-existent master file) are reported
+ /// by the callbacks during load() or loadIncremental().
+ ///
+ /// \param master_file Path to the file to load.
+ /// \param zone_origin The origin of zone to be expected inside
+ /// the master file. Currently unused, but it is expected to
+ /// be used for some validation.
+ /// \param zone_class The class of zone to be expected inside the
+ /// master file.
+ /// \param callbacks The callbacks by which it should report problems.
+ /// Usually, the callback carries a filename and line number of the
+ /// input where the problem happens. There's a special case of empty
+ /// filename and zero line in case the opening of the top-level master
+ /// file fails.
+ /// \param add_callback The callback which would be called with each
+ /// loaded RR.
+ /// \param options Options for the parsing, which is bitwise-or of
+ /// the Options values or DEFAULT. If the MANY_ERRORS option is
+ /// included, the parser tries to continue past errors. If it
+ /// is not included, it stops at first encountered error.
+ /// \throw std::bad_alloc when there's not enough memory.
+ /// \throw isc::InvalidParameter if add_callback is empty.
+ MasterLoader(const char* master_file,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options = DEFAULT);
+
+ /// \brief Constructor from a stream
+ ///
+ /// This is a constructor very similar to the previous one. The only
+ /// difference is it doesn't take a filename, but an input stream
+ /// to read the data from. It is expected to be mostly used in tests,
+ /// but it is public as it may possibly be useful for other currently
+ /// unknown purposes.
+ MasterLoader(std::istream& input,
+ const Name& zone_origin,
+ const RRClass& zone_class,
+ const MasterLoaderCallbacks& callbacks,
+ const AddRRCallback& add_callback,
+ Options options = DEFAULT);
+
+ /// \brief Destructor
+ ~MasterLoader();
+
+ /// \brief Load some RRs
+ ///
+ /// This method loads at most count_limit RRs and reports them. In case
+ /// an error (either fatal or without MANY_ERRORS) or end of file is
+ /// encountered, they may be less.
+ ///
+ /// \param count_limit Upper limit on the number of RRs loaded.
+ /// \return In case it stops because of the count limit, it returns false.
+ /// It returns true if the loading is done.
+ /// \throw isc::InvalidOperation when called after loading was done
+ /// already.
+ /// \throw MasterLoaderError when there's an error in the input master
+ /// file and the MANY_ERRORS is not specified. It never throws this
+ /// in case MANY_ERRORS is specified.
+ bool loadIncremental(size_t count_limit);
+
+ /// \brief Load everything
+ ///
+ /// This simply calls loadIncremental until the loading is done.
+ /// \throw isc::InvalidOperation when called after loading was done
+ /// already.
+ /// \throw MasterLoaderError when there's an error in the input master
+ /// file and the MANY_ERRORS is not specified. It never throws this
+ /// in case MANY_ERRORS is specified.
+ void load() {
+ while (!loadIncremental(1000)) { // 1000 = arbitrary largish number
+ // Body intentionally left blank
+ }
+ }
+
+ /// \brief Was the loading successful?
+ ///
+ /// \return true if and only if the loading was complete (after a call of
+ /// load or after loadIncremental returned true) and there was no
+ /// error. In other cases, return false.
+ /// \note While this method works even before the loading is complete (by
+ /// returning false in that case), it is meant to be called only after
+ /// finishing the load.
+ bool loadedSucessfully() const;
+
+ /// \brief Return the total size of the zone files and streams.
+ ///
+ /// This method returns the size of the source of the zone to be loaded
+ /// (master zone files or streams) that is known at the time of the call.
+ /// For a zone file, it's the size of the file; for a stream, it's the
+ /// size of the data (in bytes) available at the start of the load.
+ /// If separate zone files are included via the $INCLUDE directive, the
+ /// sum of the sizes of these files are added.
+ ///
+ /// If the loader is constructed with a stream, the size can be
+ /// "unknown" as described for \c MasterLexer::getTotalSourceSize().
+ /// In this case this method always returns
+ /// \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// If the loader is constructed with a zone file, this method
+ /// initially returns 0. So until either \c load() or \c loadIncremental()
+ /// is called, the value is meaningless.
+ ///
+ /// Note that when the source includes separate files, this method
+ /// cannot take into account the included files that the loader has not
+ /// recognized at the time of call. So it's possible that this method
+ /// returns different values at different times of call.
+ ///
+ /// \throw None
+ size_t getSize() const;
+
+ /// \brief Return the position of the loader in zone.
+ ///
+ /// This method returns a conceptual "position" of the loader in the
+ /// zone to be loaded. Specifically, it returns the total number of
+ /// characters contained in the zone files and streams and recognized
+ /// by the loader. Before starting the load it returns 0; on successful
+ /// completion it will be equal to the return value of \c getSize()
+ /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN).
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
+private:
+ class MasterLoaderImpl;
+ MasterLoaderImpl* impl_;
+};
+
+} // end namespace dns
+} // end namespace isc
+
+#endif // MASTER_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_loader_callbacks.cc b/src/lib/dns/master_loader_callbacks.cc
new file mode 100644
index 0000000..434db9b
--- /dev/null
+++ b/src/lib/dns/master_loader_callbacks.cc
@@ -0,0 +1,34 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_loader_callbacks.h>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+namespace {
+void
+nullCallback(const std::string&, size_t, const std::string&) {
+}
+}
+
+MasterLoaderCallbacks
+MasterLoaderCallbacks::getNullCallbacks() {
+ return (MasterLoaderCallbacks(nullCallback, nullCallback));
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
new file mode 100644
index 0000000..e725595
--- /dev/null
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -0,0 +1,142 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef MASTER_LOADER_CALLBACKS_H
+#define MASTER_LOADER_CALLBACKS_H
+
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dns {
+class Name;
+class RRClass;
+class RRType;
+class RRTTL;
+namespace rdata {
+class Rdata;
+typedef boost::shared_ptr<Rdata> RdataPtr;
+}
+
+/// \brief Type of callback to add a RR.
+///
+/// This type of callback is used by the loader to report another loaded
+/// RR. The Rdata is no longer preserved by the loader and is fully
+/// owned by the callback.
+///
+/// \param name The domain name where the RR belongs.
+/// \param rrclass The class of the RR.
+/// \param rrtype Type of the RR.
+/// \param rrttl Time to live of the RR.
+/// \param rdata The actual carried data of the RR.
+typedef boost::function<void(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const rdata::RdataPtr& rdata)>
+ AddRRCallback;
+
+/// \brief Set of issue callbacks for a loader.
+///
+/// This holds a set of callbacks by which a loader (such as MasterLoader)
+/// can report loaded RRsets, errors and other unusual conditions.
+///
+/// All the callbacks must be set.
+class MasterLoaderCallbacks {
+public:
+ /// \brief Type of one callback to report problems.
+ ///
+ /// This is the type of one callback used to report an unusual
+ /// condition or error.
+ ///
+ /// \param source_name The name of the source where the problem happened.
+ /// This is usually a file name.
+ /// \param source_line Position of the problem, counted in lines from the
+ /// beginning of the source.
+ /// \param reason Human readable description of what happened.
+ typedef boost::function<void(const std::string& source_name,
+ size_t source_line,
+ const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor
+ ///
+ /// Initializes the callbacks.
+ ///
+ /// \param error The error callback to use.
+ /// \param warning The warning callback to use.
+ /// \throw isc::InvalidParameter if any of the callbacks is empty.
+ MasterLoaderCallbacks(const IssueCallback& error,
+ const IssueCallback& warning) :
+ error_(error),
+ warning_(warning)
+ {
+ if (error_.empty() || warning_.empty()) {
+ isc_throw(isc::InvalidParameter,
+ "Empty function passed as callback");
+ }
+ }
+
+ /// \brief Call callback for serious errors
+ ///
+ /// This is called whenever there's a serious problem which makes the data
+ /// being loaded unusable. Further processing may or may not happen after
+ /// this (for example to detect further errors), but the data should not
+ /// be used.
+ ///
+ /// It calls whatever was passed to the error parameter to the constructor.
+ ///
+ /// If the caller of the loader wants to abort, it is possible to throw
+ /// from the callback, which aborts the load.
+ void error(const std::string& source_name, size_t source_line,
+ const std::string& reason) const
+ {
+ error_(source_name, source_line, reason);
+ }
+
+ /// \brief Call callback for potential problems
+ ///
+ /// This is called whenever a minor problem is discovered. This might mean
+ /// the data is completely OK, it just looks suspicious.
+ ///
+ /// It calls whatever was passed to the warn parameter to the constructor.
+ ///
+ /// The loading will continue after the callback. If the caller wants to
+ /// abort (which is probably not a very good idea, since warnings
+ /// may be false positives), it is possible to throw from inside the
+ /// callback.
+ void warning(const std::string& source_name, size_t source_line,
+ const std::string& reason) const
+ {
+ warning_(source_name, source_line, reason);
+ }
+
+ /// \brief Return a callbacks instance with null callbacks
+ ///
+ /// This is a convenience wrapper to generate a
+ /// \c MasterLoaderCallbacks object with both callbacks being nothing.
+ /// This will be useful for applications that only need to run
+ /// \c MasterLoader and get the end result.
+ ///
+ /// \throw None
+ static MasterLoaderCallbacks getNullCallbacks();
+
+private:
+ const IssueCallback error_, warning_;
+};
+
+}
+}
+
+#endif // MASTER_LOADER_CALLBACKS_H
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6dd94b6..a221bfe 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -25,6 +25,9 @@ libb10_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
libb10_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
+libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
+libb10_pydnspp_la_SOURCES += rrset_collection_python.h
+libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -43,6 +46,8 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
EXTRA_DIST = tsigerror_python_inc.cc
EXTRA_DIST += message_python_inc.cc
EXTRA_DIST += nsec3hash_python_inc.cc
+EXTRA_DIST += rrset_collection_python_inc.cc
+EXTRA_DIST += zone_checker_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 23ed463..c75c737 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -50,12 +50,16 @@
#include "rrset_python.h"
#include "rrttl_python.h"
#include "rrtype_python.h"
+#include "rrset_collection_python.h"
#include "serial_python.h"
#include "tsigerror_python.h"
#include "tsigkey_python.h"
#include "tsig_python.h"
#include "tsig_rdata_python.h"
#include "tsigrecord_python.h"
+#include "zone_checker_python.h"
+
+#include "zone_checker_python_inc.cc"
using namespace isc::dns;
using namespace isc::dns::python;
@@ -65,21 +69,13 @@ namespace {
bool
initModulePart_EDNS(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- //
// After the type has been initialized, we initialize any exceptions
// that are defined in the wrapper for this class, and add constants
// to the type, if any
- if (PyType_Ready(&edns_type) < 0) {
+ if (!initClass(edns_type, "EDNS", mod)) {
return (false);
}
- Py_INCREF(&edns_type);
- void* p = &edns_type;
- PyModule_AddObject(mod, "EDNS", static_cast<PyObject*>(p));
-
addClassVariable(edns_type, "SUPPORTED_VERSION",
Py_BuildValue("B", EDNS::SUPPORTED_VERSION));
@@ -88,14 +84,9 @@ initModulePart_EDNS(PyObject* mod) {
bool
initModulePart_Message(PyObject* mod) {
- if (PyType_Ready(&message_type) < 0) {
+ if (!initClass(message_type, "Message", mod)) {
return (false);
}
- void* p = &message_type;
- if (PyModule_AddObject(mod, "Message", static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&message_type);
try {
//
@@ -186,32 +177,25 @@ initModulePart_Message(PyObject* mod) {
bool
initModulePart_MessageRenderer(PyObject* mod) {
- if (PyType_Ready(&messagerenderer_type) < 0) {
+ if (!initClass(messagerenderer_type, "MessageRenderer", mod)) {
return (false);
}
- Py_INCREF(&messagerenderer_type);
addClassVariable(messagerenderer_type, "CASE_INSENSITIVE",
Py_BuildValue("I", MessageRenderer::CASE_INSENSITIVE));
addClassVariable(messagerenderer_type, "CASE_SENSITIVE",
Py_BuildValue("I", MessageRenderer::CASE_SENSITIVE));
- PyModule_AddObject(mod, "MessageRenderer",
- reinterpret_cast<PyObject*>(&messagerenderer_type));
return (true);
}
bool
-initModulePart_Name(PyObject* mod) {
-
- //
- // NameComparisonResult
- //
- if (PyType_Ready(&name_comparison_result_type) < 0) {
+initModulePart_NameComparisonResult(PyObject* mod) {
+ if (!initClass(name_comparison_result_type,
+ "NameComparisonResult", mod)) {
return (false);
}
- Py_INCREF(&name_comparison_result_type);
// Add the enums to the module
po_NameRelation = Py_BuildValue("{i:s,i:s,i:s,i:s}",
@@ -231,17 +215,14 @@ initModulePart_Name(PyObject* mod) {
addClassVariable(name_comparison_result_type, "COMMONANCESTOR",
Py_BuildValue("I", NameComparisonResult::COMMONANCESTOR));
- PyModule_AddObject(mod, "NameComparisonResult",
- reinterpret_cast<PyObject*>(&name_comparison_result_type));
-
- //
- // Name
- //
+ return (true);
+}
- if (PyType_Ready(&name_type) < 0) {
+bool
+initModulePart_Name(PyObject* mod) {
+ if (!initClass(name_type, "Name", mod)) {
return (false);
}
- Py_INCREF(&name_type);
// Add the constants to the module
addClassVariable(name_type, "MAX_WIRE",
@@ -260,51 +241,56 @@ initModulePart_Name(PyObject* mod) {
addClassVariable(name_type, "ROOT_NAME",
createNameObject(Name::ROOT_NAME()));
- PyModule_AddObject(mod, "Name",
- reinterpret_cast<PyObject*>(&name_type));
-
-
// Add the exceptions to the module
- po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
- PyModule_AddObject(mod, "EmptyLabel", po_EmptyLabel);
+ try {
+ po_EmptyLabel = PyErr_NewException("pydnspp.EmptyLabel", NULL, NULL);
+ PyObjectContainer(po_EmptyLabel).installToModule(mod, "EmptyLabel");
- po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
- PyModule_AddObject(mod, "TooLongName", po_TooLongName);
+ po_TooLongName = PyErr_NewException("pydnspp.TooLongName", NULL, NULL);
+ PyObjectContainer(po_TooLongName).installToModule(mod, "TooLongName");
- po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
- PyModule_AddObject(mod, "TooLongLabel", po_TooLongLabel);
+ po_TooLongLabel = PyErr_NewException("pydnspp.TooLongLabel", NULL, NULL);
+ PyObjectContainer(po_TooLongLabel).installToModule(mod, "TooLongLabel");
- po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
- PyModule_AddObject(mod, "BadLabelType", po_BadLabelType);
+ po_BadLabelType = PyErr_NewException("pydnspp.BadLabelType", NULL, NULL);
+ PyObjectContainer(po_BadLabelType).installToModule(mod, "BadLabelType");
- po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
- PyModule_AddObject(mod, "BadEscape", po_BadEscape);
+ po_BadEscape = PyErr_NewException("pydnspp.BadEscape", NULL, NULL);
+ PyObjectContainer(po_BadEscape).installToModule(mod, "BadEscape");
- po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL, NULL);
- PyModule_AddObject(mod, "IncompleteName", po_IncompleteName);
+ po_IncompleteName = PyErr_NewException("pydnspp.IncompleteName", NULL,
+ NULL);
+ PyObjectContainer(po_IncompleteName).installToModule(mod, "IncompleteName");
- po_InvalidBufferPosition =
- PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
- PyModule_AddObject(mod, "InvalidBufferPosition", po_InvalidBufferPosition);
+ po_InvalidBufferPosition =
+ PyErr_NewException("pydnspp.InvalidBufferPosition", NULL, NULL);
+ PyObjectContainer(po_InvalidBufferPosition).installToModule(
+ mod, "InvalidBufferPosition");
- // This one could have gone into the message_python.cc file, but is
- // already needed here.
- po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
- NULL, NULL);
- PyModule_AddObject(mod, "DNSMessageFORMERR", po_DNSMessageFORMERR);
+ // This one could have gone into the message_python.cc file, but is
+ // already needed here.
+ po_DNSMessageFORMERR = PyErr_NewException("pydnspp.DNSMessageFORMERR",
+ NULL, NULL);
+ PyObjectContainer(po_DNSMessageFORMERR).installToModule(
+ mod, "DNSMessageFORMERR");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in Name initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in Name initialization");
+ return (false);
+ }
return (true);
}
bool
initModulePart_Opcode(PyObject* mod) {
- if (PyType_Ready(&opcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&opcode_type);
- void* p = &opcode_type;
- if (PyModule_AddObject(mod, "Opcode", static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&opcode_type);
+ if (!initClass(opcode_type, "Opcode", mod)) {
return (false);
}
@@ -346,25 +332,12 @@ initModulePart_Opcode(PyObject* mod) {
bool
initModulePart_Question(PyObject* mod) {
- if (PyType_Ready(&question_type) < 0) {
- return (false);
- }
- Py_INCREF(&question_type);
- PyModule_AddObject(mod, "Question",
- reinterpret_cast<PyObject*>(&question_type));
-
- return (true);
+ return (initClass(question_type, "Question", mod));
}
bool
initModulePart_Rcode(PyObject* mod) {
- if (PyType_Ready(&rcode_type) < 0) {
- return (false);
- }
- Py_INCREF(&rcode_type);
- void* p = &rcode_type;
- if (PyModule_AddObject(mod, "Rcode", static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&rcode_type);
+ if (!initClass(rcode_type, "Rcode", mod)) {
return (false);
}
@@ -408,126 +381,168 @@ initModulePart_Rcode(PyObject* mod) {
bool
initModulePart_Rdata(PyObject* mod) {
- if (PyType_Ready(&rdata_type) < 0) {
+ if (!initClass(rdata_type, "Rdata", mod)) {
return (false);
}
- Py_INCREF(&rdata_type);
- PyModule_AddObject(mod, "Rdata",
- reinterpret_cast<PyObject*>(&rdata_type));
// Add the exceptions to the class
- po_InvalidRdataLength = PyErr_NewException("pydnspp.InvalidRdataLength",
- NULL, NULL);
- PyModule_AddObject(mod, "InvalidRdataLength", po_InvalidRdataLength);
-
- po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText",
- NULL, NULL);
- PyModule_AddObject(mod, "InvalidRdataText", po_InvalidRdataText);
-
- po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong",
- NULL, NULL);
- PyModule_AddObject(mod, "CharStringTooLong", po_CharStringTooLong);
-
+ try {
+ po_InvalidRdataLength =
+ PyErr_NewException("pydnspp.InvalidRdataLength", NULL, NULL);
+ PyObjectContainer(po_InvalidRdataLength).installToModule(
+ mod, "InvalidRdataLength");
+
+ po_InvalidRdataText = PyErr_NewException("pydnspp.InvalidRdataText",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidRdataText).installToModule(
+ mod, "InvalidRdataText");
+
+ po_CharStringTooLong = PyErr_NewException("pydnspp.CharStringTooLong",
+ NULL, NULL);
+ PyObjectContainer(po_CharStringTooLong).installToModule(
+ mod, "CharStringTooLong");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in Rdata initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in Rdata initialization");
+ return (false);
+ }
return (true);
}
bool
initModulePart_RRClass(PyObject* mod) {
- po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass",
- NULL, NULL);
- Py_INCREF(po_InvalidRRClass);
- PyModule_AddObject(mod, "InvalidRRClass", po_InvalidRRClass);
- po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass",
- NULL, NULL);
- Py_INCREF(po_IncompleteRRClass);
- PyModule_AddObject(mod, "IncompleteRRClass", po_IncompleteRRClass);
+ if (!initClass(rrclass_type, "RRClass", mod)) {
+ return (false);
+ }
+
+ try {
+ po_InvalidRRClass = PyErr_NewException("pydnspp.InvalidRRClass",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidRRClass).installToModule(
+ mod, "InvalidRRClass");
- if (PyType_Ready(&rrclass_type) < 0) {
+ po_IncompleteRRClass = PyErr_NewException("pydnspp.IncompleteRRClass",
+ NULL, NULL);
+ PyObjectContainer(po_IncompleteRRClass).installToModule(
+ mod, "IncompleteRRClass");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRClass initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in RRClass initialization");
return (false);
}
- Py_INCREF(&rrclass_type);
- PyModule_AddObject(mod, "RRClass",
- reinterpret_cast<PyObject*>(&rrclass_type));
return (true);
}
bool
initModulePart_RRset(PyObject* mod) {
- po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
- PyModule_AddObject(mod, "EmptyRRset", po_EmptyRRset);
+ if (!initClass(rrset_type, "RRset", mod)) {
+ return (false);
+ }
- // NameComparisonResult
- if (PyType_Ready(&rrset_type) < 0) {
+ try {
+ po_EmptyRRset = PyErr_NewException("pydnspp.EmptyRRset", NULL, NULL);
+ PyObjectContainer(po_EmptyRRset).installToModule(mod, "EmptyRRset");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRset initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in RRset initialization");
return (false);
}
- Py_INCREF(&rrset_type);
- PyModule_AddObject(mod, "RRset",
- reinterpret_cast<PyObject*>(&rrset_type));
return (true);
}
bool
initModulePart_RRTTL(PyObject* mod) {
- po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRRTTL", po_InvalidRRTTL);
- po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL",
- NULL, NULL);
- PyModule_AddObject(mod, "IncompleteRRTTL", po_IncompleteRRTTL);
+ if (!initClass(rrttl_type, "RRTTL", mod)) {
+ return (false);
+ }
- if (PyType_Ready(&rrttl_type) < 0) {
+ try {
+ po_InvalidRRTTL = PyErr_NewException("pydnspp.InvalidRRTTL",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidRRTTL).installToModule(mod,
+ "InvalidRRTTL");
+
+ po_IncompleteRRTTL = PyErr_NewException("pydnspp.IncompleteRRTTL",
+ NULL, NULL);
+ PyObjectContainer(po_IncompleteRRTTL).installToModule(
+ mod, "IncompleteRRTTL");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRTTL initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in RRTTL initialization");
return (false);
}
- Py_INCREF(&rrttl_type);
- PyModule_AddObject(mod, "RRTTL",
- reinterpret_cast<PyObject*>(&rrttl_type));
return (true);
}
bool
initModulePart_RRType(PyObject* mod) {
- // Add the exceptions to the module
- po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType", NULL, NULL);
- PyModule_AddObject(mod, "InvalidRRType", po_InvalidRRType);
- po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType",
- NULL, NULL);
- PyModule_AddObject(mod, "IncompleteRRType", po_IncompleteRRType);
+ if (!initClass(rrtype_type, "RRType", mod)) {
+ return (false);
+ }
+
+ try {
+ po_InvalidRRType = PyErr_NewException("pydnspp.InvalidRRType",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidRRType).installToModule(mod,
+ "InvalidRRType");
- if (PyType_Ready(&rrtype_type) < 0) {
+ po_IncompleteRRType = PyErr_NewException("pydnspp.IncompleteRRType",
+ NULL, NULL);
+ PyObjectContainer(po_IncompleteRRType).installToModule(
+ mod, "IncompleteRRType");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRType initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in RRType initialization");
return (false);
}
- Py_INCREF(&rrtype_type);
- PyModule_AddObject(mod, "RRType",
- reinterpret_cast<PyObject*>(&rrtype_type));
return (true);
}
bool
initModulePart_Serial(PyObject* mod) {
- if (PyType_Ready(&serial_type) < 0) {
- return (false);
- }
- Py_INCREF(&serial_type);
- PyModule_AddObject(mod, "Serial",
- reinterpret_cast<PyObject*>(&serial_type));
-
- return (true);
+ return (initClass(serial_type, "Serial", mod));
}
bool
initModulePart_TSIGError(PyObject* mod) {
- if (PyType_Ready(&tsigerror_type) < 0) {
+ if (!initClass(tsigerror_type, "TSIGError", mod)) {
return (false);
}
- void* p = &tsigerror_type;
- if (PyModule_AddObject(mod, "TSIGError", static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsigerror_type);
try {
// Constant class variables
@@ -595,14 +610,9 @@ initModulePart_TSIGError(PyObject* mod) {
bool
initModulePart_TSIGKey(PyObject* mod) {
- if (PyType_Ready(&tsigkey_type) < 0) {
+ if (!initClass(tsigkey_type, "TSIGKey", mod)) {
return (false);
}
- void* p = &tsigkey_type;
- if (PyModule_AddObject(mod, "TSIGKey", static_cast<PyObject*>(p)) != 0) {
- return (false);
- }
- Py_INCREF(&tsigkey_type);
try {
// Constant class variables
@@ -635,14 +645,7 @@ initModulePart_TSIGKey(PyObject* mod) {
bool
initModulePart_TSIGKeyRing(PyObject* mod) {
- if (PyType_Ready(&tsigkeyring_type) < 0) {
- return (false);
- }
- Py_INCREF(&tsigkeyring_type);
- void* p = &tsigkeyring_type;
- if (PyModule_AddObject(mod, "TSIGKeyRing",
- static_cast<PyObject*>(p)) != 0) {
- Py_DECREF(&tsigkeyring_type);
+ if (!initClass(tsigkeyring_type, "TSIGKeyRing", mod)) {
return (false);
}
@@ -658,15 +661,9 @@ initModulePart_TSIGKeyRing(PyObject* mod) {
bool
initModulePart_TSIGContext(PyObject* mod) {
- if (PyType_Ready(&tsigcontext_type) < 0) {
+ if (!initClass(tsigcontext_type, "TSIGContext", mod)) {
return (false);
}
- void* p = &tsigcontext_type;
- if (PyModule_AddObject(mod, "TSIGContext",
- static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsigcontext_type);
try {
// Class specific exceptions
@@ -707,28 +704,14 @@ initModulePart_TSIGContext(PyObject* mod) {
bool
initModulePart_TSIG(PyObject* mod) {
- if (PyType_Ready(&tsig_type) < 0) {
- return (false);
- }
- void* p = &tsig_type;
- if (PyModule_AddObject(mod, "TSIG", static_cast<PyObject*>(p)) < 0) {
- return (false);
- }
- Py_INCREF(&tsig_type);
-
- return (true);
+ return (initClass(tsig_type, "TSIG", mod));
}
bool
initModulePart_TSIGRecord(PyObject* mod) {
- if (PyType_Ready(&tsigrecord_type) < 0) {
- return (false);
- }
- void* p = &tsigrecord_type;
- if (PyModule_AddObject(mod, "TSIGRecord", static_cast<PyObject*>(p)) < 0) {
+ if (!initClass(tsigrecord_type, "TSIGRecord", mod)) {
return (false);
}
- Py_INCREF(&tsigrecord_type);
try {
// Constant class variables
@@ -749,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
return (true);
}
+PyMethodDef methods[] = {
+ { "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
+ { NULL, NULL, 0, NULL }
+};
+
PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
@@ -758,13 +746,13 @@ PyModuleDef pydnspp = {
"and OutputBuffer for instance), and others may be necessary, but "
"were not up to now.",
-1,
- NULL,
+ methods,
NULL,
NULL,
NULL,
NULL
};
-}
+} // unnamed namespace
PyMODINIT_FUNC
PyInit_pydnspp(void) {
@@ -773,16 +761,38 @@ PyInit_pydnspp(void) {
return (NULL);
}
- // Add the exceptions to the class
- po_IscException = PyErr_NewException("pydnspp.IscException", NULL, NULL);
- PyModule_AddObject(mod, "IscException", po_IscException);
-
- po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
- NULL, NULL);
- PyModule_AddObject(mod, "InvalidParameter", po_InvalidParameter);
+ try {
+ // Add the exceptions to the class
+ po_IscException = PyErr_NewException("pydnspp.IscException", NULL, NULL);
+ PyObjectContainer(po_IscException).installToModule(mod, "IscException");
+
+ po_InvalidOperation = PyErr_NewException("pydnspp.InvalidOperation",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidOperation).installToModule(
+ mod, "InvalidOperation");
+
+ po_InvalidParameter = PyErr_NewException("pydnspp.InvalidParameter",
+ NULL, NULL);
+ PyObjectContainer(po_InvalidParameter).installToModule(
+ mod, "InvalidParameter");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in pydnspp initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in pydnspp initialization");
+ return (NULL);
+ }
// for each part included above, we call its specific initializer
+ if (!initModulePart_NameComparisonResult(mod)) {
+ return (NULL);
+ }
+
if (!initModulePart_Name(mod)) {
return (NULL);
}
@@ -863,5 +873,13 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_RRsetCollectionBase(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_RRsetCollection(mod)) {
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/dns/python/pydnspp_common.cc b/src/lib/dns/python/pydnspp_common.cc
index 8e623c5..e9d62e0 100644
--- a/src/lib/dns/python/pydnspp_common.cc
+++ b/src/lib/dns/python/pydnspp_common.cc
@@ -47,6 +47,7 @@ namespace dns {
namespace python {
// For our 'general' isc::Exceptions
PyObject* po_IscException;
+PyObject* po_InvalidOperation;
PyObject* po_InvalidParameter;
// For our own isc::dns::Exception
@@ -91,6 +92,22 @@ addClassVariable(PyTypeObject& c, const char* name, PyObject* obj) {
}
return (PyDict_SetItemString(c.tp_dict, name, obj));
}
+
+bool
+initClass(PyTypeObject& type, const char* name, PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ //
+ void* p = &type;
+ if (PyType_Ready(&type) < 0 ||
+ PyModule_AddObject(mod, name, static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&type);
+ return (true);
+}
+
}
}
}
diff --git a/src/lib/dns/python/pydnspp_common.h b/src/lib/dns/python/pydnspp_common.h
index 5ca1cd8..4095f54 100644
--- a/src/lib/dns/python/pydnspp_common.h
+++ b/src/lib/dns/python/pydnspp_common.h
@@ -28,6 +28,7 @@ namespace dns {
namespace python {
// For our 'general' isc::Exceptions
extern PyObject* po_IscException;
+extern PyObject* po_InvalidOperation;
extern PyObject* po_InvalidParameter;
// For our own isc::dns::Exception
@@ -47,6 +48,18 @@ int readDataFromSequence(uint8_t *data, size_t len, PyObject* sequence);
int addClassVariable(PyTypeObject& c, const char* name, PyObject* obj);
+/// \brief Initialize a wrapped class type, and add to module
+///
+/// The type object is initalized, and its refcount is increased after
+/// successful addition to the module.
+///
+/// \param type The type object to initialize
+/// \param name The python name of the class to add
+/// \param mod The python module to add the class to
+///
+/// \return true on success, false on failure
+bool initClass(PyTypeObject& type, const char* name, PyObject* mod);
+
// Short term workaround for unifying the return type of tp_hash
#if PY_MINOR_VERSION < 2
typedef long Py_hash_t;
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
new file mode 100644
index 0000000..df313f7
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -0,0 +1,426 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/name_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrset_collection.h>
+
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "rrset_collection_python_inc.cc"
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+//
+// RRsetCollectionBase
+//
+
+namespace {
+int
+RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
+ PyErr_SetString(PyExc_TypeError,
+ "RRsetCollectionBase cannot be instantiated directly");
+ return (-1);
+}
+
+void
+RRsetCollectionBase_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+ if (self->cppobj == NULL) {
+ PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
+ "derived RRsetCollection class");
+ return (NULL);
+ }
+
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ ConstRRsetPtr found_rrset = self->cppobj->find(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (found_rrset) {
+ return (createRRsetObject(*found_rrset));
+ }
+ Py_RETURN_NONE;
+ }
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollectionBase.find: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollectionBase_methods[] = {
+ { "find", RRsetCollectionBase_find, METH_VARARGS,
+ RRsetCollectionBase_find_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_base_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollectionBase",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollectionBase_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags (it's inheritable)
+ RRsetCollectionBase_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollectionBase_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollectionBase_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollectionBase(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&rrset_collection_base_type) < 0) {
+ return (false);
+ }
+ void* p = &rrset_collection_base_type;
+ if (PyModule_AddObject(mod, "RRsetCollectionBase",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrset_collection_base_type);
+
+ return (true);
+}
+
+//
+// RRsetCollection
+//
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_RRsetCollection, RRsetCollection> RRsetCollectionContainer;
+
+int
+RRsetCollection_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ const char* filename;
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ Py_buffer py_buf;
+
+ if (PyArg_ParseTuple(args, "sO!O!", &filename, &name_type, &po_name,
+ &rrclass_type, &po_rrclass)) {
+ self->cppobj =
+ new RRsetCollection(filename, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "y*O!O!", &py_buf, &name_type,
+ &po_name,&rrclass_type, &po_rrclass)) {
+ PyErr_Clear(); // clear the error for the first ParseTuple
+ const char* const cp = static_cast<const char*>(py_buf.buf);
+ std::istringstream iss(string(cp, cp + py_buf.len));
+ self->cppobj =
+ new RRsetCollection(iss, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "")) {
+ PyErr_Clear(); // clear the error for the second ParseTuple
+ self->cppobj = new RRsetCollection;
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct RRsetCollection object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (-1);
+ }
+
+ // Default error string isn't helpful when it takes multiple combinations
+ // of args. We provide our own.
+ PyErr_SetString(PyExc_TypeError, "Invalid argument(s) to RRsetCollection "
+ "constructor; see pydoc");
+
+ return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollection_addRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_rrset;
+ if (PyArg_ParseTuple(args, "O", &po_rrset)) {
+ if (!PyRRset_Check(po_rrset)) {
+ return (setTypeError(po_rrset, "rrset", "RRset"));
+ }
+ static_cast<RRsetCollection*>(self->cppobj)->addRRset(
+ PyRRset_ToRRsetPtr(po_rrset));
+ Py_RETURN_NONE;
+ }
+ } catch (const InvalidParameter& ex) { // duplicate add
+ PyErr_SetString(PyExc_ValueError, ex.what());
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollection.add_rrset: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+PyObject*
+RRsetCollection_removeRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ const bool result =
+ static_cast<RRsetCollection*>(self->cppobj)->removeRRset(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (...) {}
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollection_methods[] = {
+ { "add_rrset", RRsetCollection_addRRset, METH_VARARGS,
+ RRsetCollection_addRRset_doc },
+ { "remove_rrset", RRsetCollection_removeRRset, METH_VARARGS,
+ RRsetCollection_removeRRset_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollection",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollection_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ RRsetCollection_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollection_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ &rrset_collection_base_type, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollection_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollection(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&rrset_collection_type) < 0) {
+ return (false);
+ }
+ void* p = &rrset_collection_type;
+ if (PyModule_AddObject(mod, "RRsetCollection",
+ static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&rrset_collection_type);
+
+ return (true);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
new file mode 100644
index 0000000..98cb84b
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_RRSETCOLLECTION_H
+#define PYTHON_RRSETCOLLECTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRsetCollectionBase;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object
+// This structure will be commonly used for all derived classes of
+// RRsetCollectionBase. cppobj will point to an instance of the specific
+// derived class.
+class s_RRsetCollection : public PyObject {
+public:
+ s_RRsetCollection() : cppobj(NULL) {}
+ RRsetCollectionBase* cppobj;
+};
+
+// Python type information for RRsetCollectionBase
+extern PyTypeObject rrset_collection_base_type;
+
+// Python type information for dns.RRsetCollection
+extern PyTypeObject rrset_collection_type;
+
+bool initModulePart_RRsetCollectionBase(PyObject* mod);
+bool initModulePart_RRsetCollection(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_RRSETCOLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
new file mode 100644
index 0000000..5c1e532
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -0,0 +1,148 @@
+namespace {
+// Modifications
+// - libdns++ => isc.dns, libdatasrc => isc.datasrc
+// - note about the constructor.
+// - add note about iteration
+const char* const RRsetCollectionBase_doc = "\
+Generic class to represent a set of RRsets.\n\
+\n\
+This is a generic container and the stored set of RRsets does not\n\
+necessarily form a valid zone (e.g. there doesn't necessarily have to\n\
+be an SOA at the \"origin\"). Instead, it will be used to represent a\n\
+single zone for the purpose of zone loading/checking. It provides a\n\
+simple find() method to find an RRset for the given name and type (and\n\
+maybe class) and a way to iterate over all RRsets.\n\
+\n\
+ Note: in the initial version, iteration is not yet supported.\n\
+\n\
+See RRsetCollection for a simple isc.dns implementation. Other modules\n\
+such as isc.datasrc will have another implementation.\n\
+\n\
+This base class cannot be directly instantiated, so no constructor is\n\
+defined.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+// - NULL => None
+// - added types of params
+const char* const RRsetCollectionBase_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, None is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name (isc.dns.Name) The name of the RRset to search for.\n\
+ rrtype (isc.dns.RRType) The type of the RRset to search for.\n\
+ rrclass (isc.dns.RRClass) The class of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, None otherwise.\n\
+";
+
+
+// Modifications
+// - libdns++ => isc.dns
+// - remove STL
+// - MasterLoaderError => IscException
+// - added types of params
+// - input_stream => input, stream => bytes
+const char* const RRsetCollection_doc = "\
+Derived class implementation of RRsetCollectionBase for isc.dns module.\n\
+\n\
+RRsetCollection()\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor creates an empty collection without any data in\n\
+ it. RRsets can be added to the collection with the add_rrset()\n\
+ method.\n\
+\n\
+RRsetCollection(filename, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ The origin and rrclass arguments are required for the zone\n\
+ loading, but RRsetCollection itself does not do any validation,\n\
+ and the collection of RRsets does not have to form a valid zone.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ filename (str) Name of a file containing a collection of RRs in the\n\
+ master file format (which may or may not form a valid\n\
+ zone).\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+RRsetCollection(input_stream, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor is similar to the previous one, but instead of\n\
+ taking a filename to load a zone from, it takes a byte object,\n\
+ representing the zone contents in text.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ input (byte) Textual representation of the zone.\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+";
+
+// Modifications
+// - void => None
+// - InvalidParameter => ValueError
+// - remove ownership related points (doesn't apply here)
+const char* const RRsetCollection_addRRset_doc = "\
+add_rrset(rrset) -> None\n\
+\n\
+Add an RRset to the collection.\n\
+\n\
+Does not do any validation whether rrset belongs to a particular zone\n\
+or not.\n\
+\n\
+It throws a ValueError exception if an rrset with the same\n\
+class, type and name already exists.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+const char* const RRsetCollection_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, NULL is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name The name of the RRset to search for.\n\
+ rrclass The class of the RRset to search for.\n\
+ rrtype The type of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, NULL otherwise.\n\
+";
+
+// Modifications
+// // - true/false => True/False
+const char* const RRsetCollection_removeRRset_doc = "\
+remove_rrset(name, rrclass, rrtype) -> bool\n\
+\n\
+Remove an RRset from the collection.\n\
+\n\
+RRset(s) matching the name, rrclass and rrtype are removed from the\n\
+collection.\n\
+\n\
+True if a matching RRset was deleted, False if no such RRset exists.\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4b0ea9f..de6b010 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,12 +12,14 @@ PYTESTS += rrclass_python_test.py
PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
+PYTESTS += rrset_collection_python_test.py
PYTESTS += serial_python_test.py
PYTESTS += tsig_python_test.py
PYTESTS += tsig_rdata_python_test.py
PYTESTS += tsigerror_python_test.py
PYTESTS += tsigkey_python_test.py
PYTESTS += tsigrecord_python_test.py
+PYTESTS += zone_checker_python_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testutil.py
diff --git a/src/lib/dns/python/tests/rdata_python_test.py b/src/lib/dns/python/tests/rdata_python_test.py
index 81dea5f..3b8f9ad 100644
--- a/src/lib/dns/python/tests/rdata_python_test.py
+++ b/src/lib/dns/python/tests/rdata_python_test.py
@@ -38,7 +38,7 @@ class RdataTest(unittest.TestCase):
self.assertRaises(InvalidRdataText, Rdata, RRType("A"), RRClass("IN"),
"Invalid Rdata Text")
self.assertRaises(CharStringTooLong, Rdata, RRType("TXT"),
- RRClass("IN"), ' ' * 256)
+ RRClass("IN"), 'x' * 256)
self.assertRaises(InvalidRdataLength, Rdata, RRType("TXT"),
RRClass("IN"), bytes(65536))
self.assertRaises(DNSMessageFORMERR, Rdata, RRType("TXT"),
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
new file mode 100644
index 0000000..2cf286e
--- /dev/null
+++ b/src/lib/dns/python/tests/rrset_collection_python_test.py
@@ -0,0 +1,140 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+import unittest
+from pydnspp import *
+
+# This should refer to the testdata diretory for the C++ tests.
+TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
+
+class RRsetCollectionBaseTest(unittest.TestCase):
+ def test_init(self):
+ # direct instantiation of the base class is prohibited.
+ self.assertRaises(TypeError, RRsetCollectionBase)
+
+class RRsetCollectionTest(unittest.TestCase):
+ def test_init_fail(self):
+ # check various failure cases on construction (other normal cases are
+ # covered as part of other tests)
+
+ # bad args
+ self.assertRaises(TypeError, RRsetCollection, 1)
+ self.assertRaises(TypeError, RRsetCollection, # extra arg
+ b'example. 0 A 192.0.2.1',
+ Name('example'), RRClass.IN(), 1)
+ self.assertRaises(TypeError, RRsetCollection, # incorrect order
+ b'example. 0 A 192.0.2.1', RRClass.IN(),
+ Name('example'))
+
+ # constructor will result in C++ exception.
+ self.assertRaises(IscException, RRsetCollection,
+ TESTDATA_DIR + '/no_such_file', Name('example.org'),
+ RRClass.IN())
+
+ def check_find_result(self, rrsets):
+ # Commonly used check pattern
+ found = rrsets.find(Name('www.example.org'), RRClass.IN(), RRType.A())
+ self.assertNotEqual(None, found)
+ self.assertEqual(Name('www.example.org'), found.get_name())
+ self.assertEqual(RRClass.IN(), found.get_class())
+ self.assertEqual(RRType.A(), found.get_type())
+ self.assertEqual('192.0.2.1', found.get_rdata()[0].to_text())
+
+ def test_find(self):
+ # Checking the underlying find() is called as intended, both for
+ # success and failure cases, and with two different constructors.
+ rrsets = RRsetCollection(TESTDATA_DIR + '/example.org',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ rrsets = RRsetCollection(b'www.example.org. 3600 IN A 192.0.2.1',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ def test_find_badargs(self):
+ rrsets = RRsetCollection()
+
+ # Check bad arguments: bad types
+ self.assertRaises(TypeError, rrsets.find, 1, RRClass.IN(), RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), 1)
+ self.assertRaises(TypeError, rrsets.find, Name('example'), RRType.A(),
+ RRClass.IN())
+
+ # Check bad arguments: too many/few arguments
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), RRType.A(), 0)
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN())
+
+ def test_add_remove_rrset(self):
+ name = Name('www.example.org')
+ rrclass = RRClass.IN()
+ rrtype = RRType.A()
+
+ # Create a collection with no RRsets
+ rrsets = RRsetCollection()
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # add the record, then it should be found
+ rrset = RRset(name, rrclass, rrtype, RRTTL(60))
+ rrset.add_rdata(Rdata(rrtype, rrclass, '192.0.2.1'))
+ self.assertEqual(None, rrsets.add_rrset(rrset))
+ self.check_find_result(rrsets)
+
+ # duplicate add is (at least currently) rejected
+ self.assertRaises(ValueError, rrsets.add_rrset, rrset)
+
+ # remove it, then we cannot find it any more.
+ self.assertTrue(rrsets.remove_rrset(name, rrclass, rrtype))
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # duplicate remove (specified RRset doesn't exist) reulsts in False
+ self.assertFalse(rrsets.remove_rrset(name, rrclass, rrtype))
+
+ # Checking bad args
+ self.assertRaises(TypeError, rrsets.add_rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset, rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset)
+
+ self.assertRaises(TypeError, rrsets.remove_rrset, 1, rrclass, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, 1, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass, 1)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrtype,
+ rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass,
+ rrtype, 1)
+
+ def test_empty_class(self):
+ # A user defined collection class shouldn't cause disruption.
+ class EmptyRRsetCollection(RRsetCollectionBase):
+ def __init__(self):
+ pass
+ rrsets = EmptyRRsetCollection()
+ self.assertRaises(TypeError, rrsets.find, Name('www.example.org'),
+ RRClass.IN(), RRType.A())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tests/tsigkey_python_test.py b/src/lib/dns/python/tests/tsigkey_python_test.py
index 516bea4..ca7a61e 100644
--- a/src/lib/dns/python/tests/tsigkey_python_test.py
+++ b/src/lib/dns/python/tests/tsigkey_python_test.py
@@ -170,6 +170,12 @@ class TSIGKeyRingTest(unittest.TestCase):
self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
self.assertEqual(self.secret, key.get_secret())
+ (code, key) = self.keyring.find(self.key_name)
+ self.assertEqual(TSIGKeyRing.SUCCESS, code)
+ self.assertEqual(self.key_name, key.get_key_name())
+ self.assertEqual(TSIGKey.HMACSHA256_NAME, key.get_algorithm_name())
+ self.assertEqual(self.secret, key.get_secret())
+
(code, key) = self.keyring.find(Name('different-key.example'),
self.sha256_name)
self.assertEqual(TSIGKeyRing.NOTFOUND, code)
diff --git a/src/lib/dns/python/tests/zone_checker_python_test.py b/src/lib/dns/python/tests/zone_checker_python_test.py
new file mode 100644
index 0000000..66b6c47
--- /dev/null
+++ b/src/lib/dns/python/tests/zone_checker_python_test.py
@@ -0,0 +1,179 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import sys
+from pydnspp import *
+
+# A separate exception class raised from some tests to see if it's propagated.
+class FakeException(Exception):
+ pass
+
+class ZoneCheckerTest(unittest.TestCase):
+ def __callback(self, reason, reasons):
+ # Issue callback for check_zone(). It simply records the given reason
+ # string in the given list.
+ reasons.append(reason)
+
+ def test_check(self):
+ errors = []
+ warns = []
+
+ # A successful case with no warning.
+ rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
+ b'example.org. 0 NS ns.example.org.\n' +
+ b'ns.example.org. 0 A 192.0.2.1\n',
+ Name('example.org'), RRClass.IN())
+ self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
+ rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ # Check fails and one additional warning.
+ rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
+ Name('example.org'), RRClass.IN())
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
+ self.assertEqual(['zone example.org/IN: NS has no address records ' +
+ '(A or AAAA)'], warns)
+
+ # Same RRset collection, suppressing callbacks
+ errors = []
+ warns = []
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (None, None)))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ def test_check_badarg(self):
+ rrsets = RRsetCollection()
+ # Bad types
+ self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ 1, (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, 1)
+
+ # Bad callbacks
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (1, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, 1))
+
+ # Extra/missing args
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None), 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets)
+ check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
+
+ def test_check_callback_fail(self):
+ # Let the call raise a Python exception. It should be propagated to
+ # the top level.
+ def __bad_callback(reason):
+ raise FakeException('error in callback')
+
+ # Using an empty collection, triggering an error callback.
+ self.assertRaises(FakeException, check_zone, Name('example.org'),
+ RRClass.IN(), RRsetCollection(),
+ (__bad_callback, None))
+
+ # An unusual case: the callback is expected to return None, but if it
+ # returns an actual object it shouldn't cause leak inside the callback.
+ class RefChecker:
+ pass
+ def __callback(reason, checker):
+ return checker
+
+ ref_checker = RefChecker()
+ orig_refcnt = sys.getrefcount(ref_checker)
+ check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
+ (lambda r: __callback(r, ref_checker), None))
+ self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
+
+ def test_check_custom_collection(self):
+ # Test if check_zone() works with pure-Python RRsetCollection.
+
+ class FakeRRsetCollection(RRsetCollectionBase):
+ # This is the Python-only collection class. Its find() makes
+ # the check pass by default, by returning hardcoded RRsets.
+ # If raise_on_find is set to True, find() raises an exception.
+ # If find_result is set to something other than 'use_default'
+ # (as a string), find() returns that specified value (note that
+ # it can be None).
+
+ def __init__(self, raise_on_find=False, find_result='use_default'):
+ self.__raise_on_find = raise_on_find
+ self.__find_result = find_result
+
+ def find(self, name, rrclass, rrtype):
+ if self.__raise_on_find:
+ raise FakeException('find error')
+ if self.__find_result is not 'use_default':
+ return self.__find_result
+ if rrtype == RRType.SOA():
+ soa = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+ '. . 0 0 0 0 0'))
+ return soa
+ if rrtype == RRType.NS():
+ ns = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
+ 'example.org'))
+ return ns
+ return None
+
+ # A successful case. Just checking it works in that case.
+ rrsets = FakeRRsetCollection()
+ self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Likewise, normal case but zone check fails.
+ rrsets = FakeRRsetCollection(False, None)
+ self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Our find() returns a bad type of result.
+ rrsets = FakeRRsetCollection(False, 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None))
+
+ # Our find() returns an empty SOA RRset. C++ zone checker code
+ # throws, which results in IscException.
+ rrsets = FakeRRsetCollection(False, RRset(Name('example'),
+ RRClass.IN(),
+ RRType.SOA(), RRTTL(0)))
+ self.assertRaises(IscException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+ # Our find() raises an exception. That exception is propagated to
+ # the top level.
+ rrsets = FakeRRsetCollection(True)
+ self.assertRaises(FakeException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tsigkey_python.cc b/src/lib/dns/python/tsigkey_python.cc
index cf79c1a..bcab59c 100644
--- a/src/lib/dns/python/tsigkey_python.cc
+++ b/src/lib/dns/python/tsigkey_python.cc
@@ -287,7 +287,9 @@ PyMethodDef TSIGKeyRing_methods[] = {
METH_VARARGS,
"Remove a TSIGKey for the given name from the TSIGKeyRing." },
{ "find", reinterpret_cast<PyCFunction>(TSIGKeyRing_find), METH_VARARGS,
- "Find a TSIGKey for the given name in the TSIGKeyRing. "
+ "Find a TSIGKey for the given name in the TSIGKeyRing. Optional "
+ "second argument is an algorithm, in which case it only returns "
+ "a key if both match.\n"
"It returns a tuple of (result_code, key)." },
{ NULL, NULL, 0, NULL }
};
@@ -362,13 +364,16 @@ TSIGKeyRing_remove(const s_TSIGKeyRing* self, PyObject* args) {
PyObject*
TSIGKeyRing_find(const s_TSIGKeyRing* self, PyObject* args) {
PyObject* key_name;
- PyObject* algorithm_name;
+ PyObject* algorithm_name = NULL;
- if (PyArg_ParseTuple(args, "O!O!", &name_type, &key_name,
+ if (PyArg_ParseTuple(args, "O!|O!", &name_type, &key_name,
&name_type, &algorithm_name)) {
- const TSIGKeyRing::FindResult result =
- self->cppobj->find(PyName_ToName(key_name),
- PyName_ToName(algorithm_name));
+ // Can't init TSIGKeyRing::FindResult without actual result,
+ // so use ternary operator
+ TSIGKeyRing::FindResult result = (algorithm_name == NULL) ?
+ self->cppobj->find(PyName_ToName(key_name)) :
+ self->cppobj->find(PyName_ToName(key_name),
+ PyName_ToName(algorithm_name));
if (result.key != NULL) {
s_TSIGKey* key = PyObject_New(s_TSIGKey, &tsigkey_type);
if (key == NULL) {
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
new file mode 100644
index 0000000..eaad72d
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.cc
@@ -0,0 +1,229 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/zone_checker_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+#include <dns/zone_checker.h>
+
+#include <boost/bind.hpp>
+
+#include <cstring>
+#include <string>
+#include <stdexcept>
+
+using std::string;
+using isc::util::python::PyObjectContainer;
+using namespace isc::dns;
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+// Place the exception class in a named namespace to avoid weird run time
+// failure with clang++. See isc.log Python wrapper.
+namespace clang_unnamed_namespace_workaround {
+// This is used to abort check_zone() and go back to the top level.
+// We use a separate exception so it won't be caught in the middle.
+class InternalException : public std::exception {
+};
+}
+using namespace clang_unnamed_namespace_workaround;
+
+namespace {
+// This is a "wrapper" RRsetCollection subclass. It's constructed with
+// a Python RRsetCollection object, and its find() calls the Python version
+// of RRsetCollection.find(). This way, the check_zone() wrapper will work
+// for pure-Python RRsetCollection classes, too.
+class PyRRsetCollection : public RRsetCollectionBase {
+public:
+ PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
+
+ virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const {
+ try {
+ // Convert C++ args to Python objects, and builds argument tuple
+ // to the Python method. This should basically succeed.
+ PyObjectContainer poc_name(createNameObject(name));
+ PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
+ PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
+ PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
+ po_rrsets_,
+ poc_name.get(),
+ poc_rrclass.get(),
+ poc_rrtype.get()));
+
+ // Call the Python method.
+ // PyObject_CallMethod is dirty and requires mutable C-string for
+ // method name and arguments. While it's unlikely for these to
+ // be modified, we err on the side of caution and make copies.
+ char method_name[sizeof("find")];
+ char method_args[sizeof("(OOO)")];
+ std::strcpy(method_name, "find");
+ std::strcpy(method_args, "(OOO)");
+ PyObjectContainer poc_result(
+ PyObject_CallMethod(po_rrsets_, method_name, method_args,
+ poc_name.get(), poc_rrclass.get(),
+ poc_rrtype.get()));
+ PyObject* const po_result = poc_result.get();
+ if (po_result == Py_None) {
+ return (ConstRRsetPtr());
+ } else if (PyRRset_Check(po_result)) {
+ return (PyRRset_ToRRsetPtr(po_result));
+ } else {
+ PyErr_SetString(PyExc_TypeError, "invalid type for "
+ "RRsetCollection.find(): must be None "
+ "or RRset");
+ throw InternalException();
+ }
+ } catch (const isc::util::python::PyCPPWrapperException& ex) {
+ // This normally means the method call fails. Propagate the
+ // already-set Python error to the top level. Other C++ exceptions
+ // are really unexpected, so we also (implicitly) propagate it
+ // to the top level and recognize it as "unexpected failure".
+ throw InternalException();
+ }
+ }
+
+ virtual IterPtr getBeginning() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+ virtual IterPtr getEnd() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+
+private:
+ PyObject* const po_rrsets_;
+};
+
+void
+callback(const string& reason, PyObject* obj) {
+ PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
+ reason.size()));
+ PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
+ if (po_result == NULL) {
+ throw InternalException();
+ }
+ Py_DECREF(po_result);
+}
+
+ZoneCheckerCallbacks::IssueCallback
+PyCallable_ToCallback(PyObject* obj) {
+ if (obj == Py_None) {
+ return (NULL);
+ }
+ return (boost::bind(callback, _1, obj));
+}
+
+}
+
+PyObject*
+pyCheckZone(PyObject*, PyObject* args) {
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrsets;
+ PyObject* po_error;
+ PyObject* po_warn;
+
+ if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
+ &po_rrsets, &po_error, &po_warn)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "zone_name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
+ }
+ if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
+ return (setTypeError(po_rrsets, "zone_rrsets",
+ "RRsetCollectionBase"));
+ }
+ if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
+ return (setTypeError(po_error, "error", "callable or None"));
+ }
+ if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
+ return (setTypeError(po_warn, "warn", "callable or None"));
+ }
+
+ PyRRsetCollection py_rrsets(po_rrsets);
+ if (checkZone(PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass), py_rrsets,
+ ZoneCheckerCallbacks(
+ PyCallable_ToCallback(po_error),
+ PyCallable_ToCallback(po_warn)))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (const InternalException& ex) {
+ // Normally, error string should have been set already. For some
+ // rare cases such as memory allocation failure, we set the last-resort
+ // error string.
+ if (PyErr_Occurred() == NULL) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in check_zone()");
+ }
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in check_zone(): " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+} // namespace internal
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/zone_checker_python.h b/src/lib/dns/python/zone_checker_python.h
new file mode 100644
index 0000000..63168fd
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_ZONE_CHECKER_H
+#define PYTHON_ZONE_CHECKER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+PyObject* pyCheckZone(PyObject* self, PyObject* args);
+
+} // namespace python
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/zone_checker_python_inc.cc b/src/lib/dns/python/zone_checker_python_inc.cc
new file mode 100644
index 0000000..c99042e
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python_inc.cc
@@ -0,0 +1,79 @@
+namespace {
+// Modifications
+// - callbacks => (error, warn)
+// - recover paragraph before itemization (it's a bug of convert script)
+// - correct broken format for nested items (another bug of script)
+// - true/false => True/False
+// - removed Exception section (for simplicity)
+const char* const dns_checkZone_doc = "\
+check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
+\n\
+Perform basic integrity checks on zone RRsets.\n\
+\n\
+This function performs some lightweight checks on zone's SOA and\n\
+(apex) NS records. Here, lightweight means it doesn't require\n\
+traversing the entire zone, and should be expected to complete\n\
+reasonably quickly regardless of the size of the zone.\n\
+\n\
+It distinguishes \"critical\" errors and other undesirable issues: the\n\
+former should be interpreted as the resulting zone shouldn't be used\n\
+further, e.g, by an authoritative server implementation; the latter\n\
+means the issues are better to be addressed but are not necessarily\n\
+considered to make the zone invalid. Critical errors are reported via\n\
+the error() function, and non critical issues are reported via warn().\n\
+\n\
+Specific checks performed by this function is as follows. Failure of\n\
+a check is considered a critical error unless noted otherwise:\n\
+\n\
+- There is exactly one SOA RR at the zone apex.\n\
+- There is at least one NS RR at the zone apex.\n\
+- For each apex NS record, if the NS name (the RDATA of the record) is\n\
+ in the zone (i.e., it's a subdomain of the zone origin and above any\n\
+ zone cut due to delegation), check the following:\n\
+ - the NS name should have an address record (AAAA or A). Failure of\n\
+ this check is considered a non critical issue.\n\
+ - the NS name does not have a CNAME. This is prohibited by Section\n\
+ 10.3 of RFC 2181.\n\
+ - the NS name is not subject to DNAME substitution. This is prohibited\n\
+ by Section 4 of RFC 6672.\n\
+\n\
+In addition, when the check is completed without any\n\
+critical error, this function guarantees that RRsets for the SOA and\n\
+(apex) NS stored in the passed RRset collection have the expected\n\
+type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
+respectively. (This is normally expected to be the case, but not\n\
+guaranteed by the API).\n\
+\n\
+As for the check on the existence of AAAA or A records for NS names,\n\
+it should be noted that BIND 9 treats this as a critical error. It's\n\
+not clear whether it's an implementation dependent behavior or based\n\
+on the protocol standard (it looks like the former), but to make it\n\
+sure we need to confirm there is even no wildcard match for the names.\n\
+This should be a very rare configuration, and more expensive to\n\
+detect, so we do not check this condition, and treat this case as a\n\
+non critical issue.\n\
+\n\
+This function indicates the result of the checks (whether there is a\n\
+critical error) via the return value: It returns True if there is no\n\
+critical error and returns False otherwise. It doesn't throw an\n\
+exception on encountering an error so that it can report as many\n\
+errors as possible in a single call. If an exception is a better way\n\
+to signal the error, the caller can pass a callable object as error()\n\
+that throws.\n\
+\n\
+This function can still throw an exception if it finds a really bogus\n\
+condition that is most likely to be an implementation bug of the\n\
+caller. Such cases include when an RRset contained in the RRset\n\
+collection is empty.\n\
+\n\
+Parameters:\n\
+ zone_name The name of the zone to be checked\n\
+ zone_class The RR class of the zone to be checked\n\
+ zone_rrsets The collection of RRsets of the zone\n\
+ error Callable object used to report errors\n\
+ warn Callable object used to report non-critical issues\n\
+\n\
+Return Value(s): True if no critical errors are found; False\n\
+otherwise.\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index c7eaa13..081f855 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -12,6 +12,20 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+
+#include <util/buffer.h>
+
+#include <dns/name.h>
+#include <dns/messagerenderer.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata.h>
+#include <dns/rrparamregistry.h>
+#include <dns/rrtype.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
#include <algorithm>
#include <cctype>
#include <string>
@@ -24,16 +38,6 @@
#include <stdint.h>
#include <string.h>
-#include <boost/lexical_cast.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <util/buffer.h>
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rrparamregistry.h>
-#include <dns/rrtype.h>
-
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
@@ -65,7 +69,7 @@ createRdata(const RRType& rrtype, const RRClass& rrclass,
RdataPtr rdata =
RRParamRegistry::getRegistry().createRdata(rrtype, rrclass, buffer,
len);
-
+
if (buffer.getPosition() - old_pos != len) {
isc_throw(InvalidRdataLength, "RDLENGTH mismatch: " <<
buffer.getPosition() - old_pos << " != " << len);
@@ -81,6 +85,94 @@ createRdata(const RRType& rrtype, const RRClass& rrclass, const Rdata& source)
source));
}
+namespace {
+void
+fromtextError(bool& error_issued, const MasterLexer& lexer,
+ MasterLoaderCallbacks& callbacks,
+ const MasterToken* token, const char* reason)
+{
+ // Don't be too noisy if there are many issues for single RDATA
+ if (error_issued) {
+ return;
+ }
+ error_issued = true;
+
+ if (token == NULL) {
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed: " + string(reason));
+ return;
+ }
+
+ switch (token->getType()) {
+ case MasterToken::STRING:
+ case MasterToken::QSTRING:
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed near '" +
+ token->getString() + "': " + string(reason));
+ break;
+ case MasterToken::ERROR:
+ callbacks.error(lexer.getSourceName(), lexer.getSourceLine(),
+ "createRdata from text failed: " +
+ token->getErrorText());
+ break;
+ default:
+ // This case shouldn't happen based on how we use MasterLexer in
+ // createRdata(), so we could assert() that here. But since it
+ // depends on detailed behavior of other classes, we treat the case
+ // in a bit less harsh way.
+ isc_throw(Unexpected, "bug: createRdata() saw unexpected token type");
+ }
+}
+}
+
+RdataPtr
+createRdata(const RRType& rrtype, const RRClass& rrclass,
+ MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks)
+{
+ RdataPtr rdata;
+
+ bool error_issued = false;
+ try {
+ rdata = RRParamRegistry::getRegistry().createRdata(
+ rrtype, rrclass, lexer, origin, options, callbacks);
+ } catch (const MasterLexer::LexerError& error) {
+ fromtextError(error_issued, lexer, callbacks, &error.token_, "");
+ } catch (const Exception& ex) {
+ // Catching all isc::Exception is too broad, but right now we don't
+ // have better granularity. When we complete #2518 we can make this
+ // finer.
+ fromtextError(error_issued, lexer, callbacks, NULL, ex.what());
+ }
+ // Other exceptions mean a serious implementation bug or fatal system
+ // error; it doesn't make sense to catch and try to recover from them
+ // here. Just propagate.
+
+ // Consume to end of line / file.
+ // Call callback via fromtextError once if there was an error.
+ do {
+ const MasterToken& token = lexer.getNextToken();
+ switch (token.getType()) {
+ case MasterToken::END_OF_LINE:
+ return (rdata);
+ case MasterToken::END_OF_FILE:
+ callbacks.warning(lexer.getSourceName(), lexer.getSourceLine(),
+ "file does not end with newline");
+ return (rdata);
+ default:
+ rdata.reset(); // we'll return NULL
+ fromtextError(error_issued, lexer, callbacks, &token,
+ "extra input text");
+ // Continue until we see EOL or EOF
+ }
+ } while (true);
+
+ // We shouldn't reach here
+ assert(false);
+ return (RdataPtr()); // add explicit return to silence some compilers
+}
+
int
compareNames(const Name& n1, const Name& n2) {
size_t len1 = n1.getLength();
@@ -119,7 +211,8 @@ Generic::Generic(isc::util::InputBuffer& buffer, size_t rdata_len) {
impl_ = new GenericImpl(data);
}
-Generic::Generic(const std::string& rdata_string) {
+void
+Generic::constructHelper(const std::string& rdata_string) {
istringstream iss(rdata_string);
string unknown_mark;
iss >> unknown_mark;
@@ -180,6 +273,34 @@ Generic::Generic(const std::string& rdata_string) {
impl_ = new GenericImpl(data);
}
+Generic::Generic(const std::string& rdata_string) {
+ constructHelper(rdata_string);
+}
+
+Generic::Generic(MasterLexer& lexer, const Name*,
+ MasterLoader::Options,
+ MasterLoaderCallbacks&)
+{
+ std::string s;
+
+ while (true) {
+ const MasterToken& token = lexer.getNextToken();
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ lexer.ungetToken(); // let the upper layer handle the end-of token
+ break;
+ }
+
+ if (!s.empty()) {
+ s += " ";
+ }
+
+ s += token.getString();
+ }
+
+ constructHelper(s);
+}
+
Generic::~Generic() {
delete impl_;
}
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index f77ea6e..3fe0c74 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -15,11 +15,15 @@
#ifndef RDATA_H
#define RDATA_H 1
-#include <stdint.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+
+#include <exceptions/exceptions.h>
#include <boost/shared_ptr.hpp>
-#include <exceptions/exceptions.h>
+#include <stdint.h>
namespace isc {
namespace util {
@@ -56,7 +60,7 @@ public:
///
/// \brief A standard DNS module exception that is thrown if RDATA parser
-/// parser encounters a character-string (as defined in RFC1035) exceeding
+/// encounters a character-string (as defined in RFC1035) exceeding
/// the maximum allowable length (\c MAX_CHARSTRING_LEN).
///
class CharStringTooLong : public Exception {
@@ -279,6 +283,11 @@ public:
/// \param rdata_len The length in buffer of the \c Rdata. In bytes.
Generic(isc::util::InputBuffer& buffer, size_t rdata_len);
+ /// \brief Constructor from master lexer.
+ ///
+ Generic(MasterLexer& lexer, const Name* name,
+ MasterLoader::Options options, MasterLoaderCallbacks& callbacks);
+
///
/// \brief The destructor.
virtual ~Generic();
@@ -367,7 +376,10 @@ public:
/// \return > 0 if \c this would be sorted after \c other.
virtual int compare(const Rdata& other) const;
//@}
+
private:
+ void constructHelper(const std::string& rdata_string);
+
GenericImpl* impl_;
};
@@ -472,6 +484,53 @@ RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
/// \c Rdata object.
RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
const Rdata& source);
+
+/// \brief Create RDATA of a given pair of RR type and class using the
+/// master lexer.
+///
+/// This is a more generic form of factory from textual RDATA, and is mainly
+/// intended to be used internally by the master file parser (\c MasterLoader)
+/// of this library.
+///
+/// The \c lexer is expected to be at the beginning of textual RDATA of the
+/// specified type and class. This function (and its underlying Rdata
+/// implementations) extracts necessary tokens from the lexer and constructs
+/// the RDATA from them.
+///
+/// Due to the intended usage of this version, this function handles error
+/// cases quite differently from other versions. It internally catches
+/// most of syntax and semantics errors of the input (reported as exceptions),
+/// calls the corresponding callback specified by the \c callbacks parameters,
+/// and returns a NULL smart pointer. If the caller rather wants to get
+/// an exception in these cases, it can pass a callback that internally
+/// throws on error. Some critical exceptions such as \c std::bad_alloc are
+/// still propagated to the upper layer as it doesn't make sense to try
+/// recovery from such a situation within this function.
+///
+/// Whether or not the creation succeeds, this function updates the lexer
+/// until it reaches either the end of line or file, starting from the end of
+/// the RDATA text (or the point of failure if the parsing fails in the
+/// middle of it). The caller can therefore assume it's ready for reading
+/// the next data (which is normally a subsequent RR in the zone file) on
+/// return, whether or not this function succeeds.
+///
+/// \param rrtype An \c RRType object specifying the type/class pair.
+/// \param rrclass An \c RRClass object specifying the type/class pair.
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of any domain name fields
+/// of the RDATA that are non absolute.
+/// \param options Master loader options controlling how to deal with errors
+/// or non critical issues in the parsed RDATA.
+/// \param callbacks Callback to be called when an error or non critical issue
+/// is found.
+/// \return An \c RdataPtr object pointing to the created
+/// \c Rdata object. Will be NULL if parsing fails.
+RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks);
+
//@}
///
@@ -511,6 +570,6 @@ int compareNames(const Name& n1, const Name& n2);
}
#endif // RDATA_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
new file mode 100644
index 0000000..4c8965a
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -0,0 +1,177 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <boost/lexical_cast.hpp>
+
+#include <cassert>
+#include <cctype>
+#include <cstring>
+#include <vector>
+
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+namespace {
+// Convert a DDD form to the corresponding integer
+int
+decimalToNumber(const char* s, const char* s_end) {
+ if (s_end - s < 3) {
+ isc_throw(InvalidRdataText, "Escaped digits too short");
+ }
+
+ const std::string num_str(s, s + 3);
+ try {
+ const int i = boost::lexical_cast<int>(num_str);
+ if (i > 255) {
+ isc_throw(InvalidRdataText, "Escaped digits too large: "
+ << num_str);
+ }
+ return (i);
+ } catch (const boost::bad_lexical_cast&) {
+ isc_throw(InvalidRdataText,
+ "Invalid form for escaped digits: " << num_str);
+ }
+}
+}
+
+void
+stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result)
+{
+ // make a space for the 1-byte length field; filled in at the end
+ result.push_back(0);
+
+ bool escape = false;
+ const char* s = str_region.beg;
+ const char* const s_end = str_region.beg + str_region.len;
+
+ for (size_t n = str_region.len; n != 0; --n, ++s) {
+ int c = (*s & 0xff);
+ if (escape && std::isdigit(c) != 0) {
+ c = decimalToNumber(s, s_end);
+ assert(n >= 3);
+ n -= 2;
+ s += 2;
+ } else if (!escape && c == '\\') {
+ escape = true;
+ continue;
+ }
+ escape = false;
+ result.push_back(c);
+ }
+ if (escape) { // terminated by non-escaped '\'
+ isc_throw(InvalidRdataText, "character-string ends with '\\'");
+ }
+ if (result.size() > MAX_CHARSTRING_LEN + 1) { // '+ 1' due to the len field
+ isc_throw(CharStringTooLong, "character-string is too long: " <<
+ (result.size() - 1) << "(+1) characters");
+ }
+ result[0] = result.size() - 1;
+}
+
+std::string
+charStringToString(const CharString& char_string) {
+ std::string s;
+ for (CharString::const_iterator it = char_string.begin() + 1;
+ it != char_string.end(); ++it) {
+ const uint8_t ch = *it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
+int compareCharStrings(const detail::CharString& self,
+ const detail::CharString& other) {
+ if (self.size() == 0 && other.size() == 0) {
+ return (0);
+ }
+ if (self.size() == 0) {
+ return (-1);
+ }
+ if (other.size() == 0) {
+ return (1);
+ }
+ const size_t self_len = self[0];
+ const size_t other_len = other[0];
+ const size_t cmp_len = std::min(self_len, other_len);
+ const int cmp = std::memcmp(&self[1], &other[1], cmp_len);
+ if (cmp < 0) {
+ return (-1);
+ } else if (cmp > 0) {
+ return (1);
+ } else if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+size_t
+bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target) {
+ if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "insufficient data to read character-string length");
+ }
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ if (buffer.getLength() - buffer.getPosition() < len) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "not enough data in buffer to read character-string of len"
+ << static_cast<int>(len));
+ }
+
+ target.resize(len + 1);
+ target[0] = len;
+ buffer.readData(&target[0] + 1, len);
+
+ return (len + 1);
+}
+
+} // end of detail
+} // end of generic
+} // end of rdata
+} // end of dns
+} // end of isc
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
new file mode 100644
index 0000000..8e3e294
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -0,0 +1,111 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_RDATA_CHARSTRING_H
+#define DNS_RDATA_CHARSTRING_H 1
+
+#include <dns/master_lexer.h>
+
+#include <string>
+#include <vector>
+#include <algorithm>
+#include <stdint.h>
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Type for DNS character string.
+///
+/// A character string can contain any unsigned 8-bit value, so this cannot
+/// be the bare char basis.
+typedef std::vector<uint8_t> CharString;
+
+/// \brief Convert a DNS character-string into corresponding binary data.
+///
+/// This helper function takes a string object that is expected to be a
+/// textual representation of a valid DNS character-string, and dumps
+/// the corresponding binary sequence in the given placeholder (passed
+/// via the \c result parameter). It handles escape notations of
+/// character-strings with a backslash ('\'), and checks the length
+/// restriction.
+///
+/// \throw CharStringTooLong The resulting binary data are too large for a
+/// valid character-string.
+/// \throw InvalidRdataText Other syntax errors.
+///
+/// \brief str_region A string that represents a character-string.
+/// \brief result A placeholder vector where the resulting data are to be
+/// stored. Expected to be empty, but it's not checked.
+void stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
+
+/// \brief Compare two CharString objects
+///
+/// \param self The CharString field to compare
+/// \param other The CharString field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+/// 1 if \c self would be sorted after \c other
+/// 0 if \c self and \c other are equal
+int compareCharStrings(const CharString& self, const CharString& other);
+
+/// \brief Convert a buffer containing a character-string to CharString
+///
+/// This method reads one character-string from the given buffer (in wire
+/// format) and places the result in the given \c CharString object.
+/// Since this is expected to be used in message parsing, the exception it
+/// raises is of that type.
+///
+/// On success, the buffer position is advanced to the end of the char-string,
+/// and the number of bytes read is returned.
+///
+/// \param buffer The buffer to read from.
+/// \param rdata_len The total size of the rr's rdata currently being read
+/// (used for integrity checks in the wire data)
+/// \param target The \c CharString where the result will be stored. Any
+/// existing data in the target will be overwritten.
+/// \throw DNSMessageFORMERR If the available data is not enough to read
+/// the character-string, or if the character-string length is out of bounds
+/// \return The number of bytes read
+size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target);
+
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_CHARSTRING_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/lexer_util.h b/src/lib/dns/rdata/generic/detail/lexer_util.h
new file mode 100644
index 0000000..89df5a0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/lexer_util.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_RDATA_LEXER_UTIL_H
+#define DNS_RDATA_LEXER_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+
+/// \file lexer_util.h
+/// \brief Utilities for extracting RDATA fields from lexer.
+///
+/// This file intends to define convenient small routines that can be
+/// commonly used in the RDATA implementation to build RDATA fields from
+/// a \c MasterLexer.
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Construct a Name object using a master lexer and optional origin.
+///
+/// This is a convenient shortcut of commonly used code pattern that would
+/// be used to build RDATA that contain a domain name field.
+///
+/// Note that this function throws an exception against invalid input.
+/// The (direct or indirect) caller's responsibility needs to expect and
+/// handle exceptions appropriately.
+///
+/// \throw MasterLexer::LexerError The next token from lexer is not string.
+/// \throw Other Exceptions from the \c Name class constructor if the next
+/// string token from the lexer does not represent a valid name.
+///
+/// \param lexer A \c MasterLexer object. Its next token is expected to be
+/// a string that represent a domain name.
+/// \param origin If non NULL, specifies the origin of the name to be
+/// constructed.
+///
+/// \return A new Name object that corresponds to the next string token of
+/// the \c lexer.
+inline Name
+createNameFromLexer(MasterLexer& lexer, const Name* origin) {
+ const MasterToken::StringRegion& str_region =
+ lexer.getNextToken(MasterToken::STRING).getStringRegion();
+ return (Name(str_region.beg, str_region.len, origin));
+}
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_LEXER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index fdab6bf..b48d109 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -15,13 +15,20 @@
#ifndef TXT_LIKE_H
#define TXT_LIKE_H 1
+#include <dns/master_lexer.h>
+#include <dns/rdata/generic/detail/char_string.h>
+
#include <stdint.h>
#include <string>
+#include <sstream>
#include <vector>
-using namespace std;
-using namespace isc::util;
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
/// \brief \c rdata::TXTLikeImpl class represents the TXT-like RDATA for TXT
/// and SPF types.
@@ -41,7 +48,7 @@ public:
///
/// \c InvalidRdataLength is thrown if rdata_len exceeds the maximum.
/// \c DNSMessageFORMERR is thrown if the RR is misformed.
- TXTLikeImpl(InputBuffer& buffer, size_t rdata_len) {
+ TXTLikeImpl(util::InputBuffer& buffer, size_t rdata_len) {
if (rdata_len > MAX_RDLENGTH) {
isc_throw(InvalidRdataLength, "RDLENGTH too large: " << rdata_len);
}
@@ -59,7 +66,7 @@ public:
" RDATA: character string length is too large: " <<
static_cast<int>(len));
}
- vector<uint8_t> data(len + 1);
+ std::vector<uint8_t> data(len + 1);
data[0] = len;
buffer.readData(&data[0] + 1, len);
string_list_.push_back(data);
@@ -70,46 +77,61 @@ public:
/// \brief Constructor from string.
///
- /// <b>Exceptions</b>
- ///
- /// \c CharStringTooLong is thrown if the parameter string length exceeds
- /// maximum.
- /// \c InvalidRdataText is thrown if the method cannot process the
- /// parameter data.
+ /// \throw CharStringTooLong the parameter string length exceeds maximum.
+ /// \throw InvalidRdataText the method cannot process the parameter data
explicit TXTLikeImpl(const std::string& txtstr) {
- // TBD: this is a simple, incomplete implementation that only supports
- // a single character-string.
+ std::istringstream ss(txtstr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ buildFromTextHelper(lexer);
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "Failed to construct " <<
+ RRType(typeCode) << " RDATA from '" << txtstr <<
+ "': extra new line");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct " <<
+ RRType(typeCode) << " RDATA from '" << txtstr << "': "
+ << ex.what());
+ }
+ }
- size_t length = txtstr.size();
- size_t pos_begin = 0;
+ /// \brief Constructor using the master lexer.
+ ///
+ /// \throw CharStringTooLong the parameter string length exceeds maximum.
+ /// \throw InvalidRdataText the method cannot process the parameter data
+ ///
+ /// \param lexer A \c MasterLexer object parsing a master file for this
+ /// RDATA.
+ TXTLikeImpl(MasterLexer& lexer) {
+ buildFromTextHelper(lexer);
+ }
- if (length > 1 && txtstr[0] == '"' && txtstr[length - 1] == '"') {
- pos_begin = 1;
- length -= 2;
+private:
+ void buildFromTextHelper(MasterLexer& lexer) {
+ while (true) {
+ const MasterToken& token = lexer.getNextToken(
+ MasterToken::QSTRING, true);
+ if (token.getType() != MasterToken::STRING &&
+ token.getType() != MasterToken::QSTRING) {
+ break;
+ }
+ string_list_.push_back(std::vector<uint8_t>());
+ stringToCharString(token.getStringRegion(), string_list_.back());
}
- if (length > MAX_CHARSTRING_LEN) {
- isc_throw(CharStringTooLong, RRType(typeCode) <<
- " RDATA construction from text:"
- " string length is too long: " << length);
- }
+ // Let upper layer handle eol/eof.
+ lexer.ungetToken();
- // TBD: right now, we don't support escaped characters
- if (txtstr.find('\\') != string::npos) {
- isc_throw(InvalidRdataText, RRType(typeCode) <<
- " RDATA from text:"
- " escaped character is currently not supported: " <<
- txtstr);
+ if (string_list_.empty()) {
+ isc_throw(InvalidRdataText, "Failed to construct" <<
+ RRType(typeCode) << " RDATA: empty input");
}
-
- vector<uint8_t> data;
- data.reserve(length + 1);
- data.push_back(length);
- data.insert(data.end(), txtstr.begin() + pos_begin,
- txtstr.begin() + pos_begin + length);
- string_list_.push_back(data);
}
+public:
/// \brief The copy constructor.
///
/// Trivial for now, we could've used the default one.
@@ -122,9 +144,9 @@ public:
///
/// \param buffer An output buffer to store the wire data.
void
- toWire(OutputBuffer& buffer) const {
- for (vector<vector<uint8_t> >::const_iterator it =
- string_list_.begin();
+ toWire(util::OutputBuffer& buffer) const {
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
it != string_list_.end();
++it)
{
@@ -139,8 +161,8 @@ public:
/// to.
void
toWire(AbstractMessageRenderer& renderer) const {
- for (vector<vector<uint8_t> >::const_iterator it =
- string_list_.begin();
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin();
it != string_list_.end();
++it)
{
@@ -151,22 +173,18 @@ public:
/// \brief Convert the TXT-like data to a string.
///
/// \return A \c string object that represents the TXT-like data.
- string
+ std::string
toText() const {
- string s;
+ std::string s;
- // XXX: this implementation is not entirely correct. for example, it
- // should escape double-quotes if they appear in the character string.
- for (vector<vector<uint8_t> >::const_iterator it =
- string_list_.begin();
- it != string_list_.end();
- ++it)
+ for (std::vector<std::vector<uint8_t> >::const_iterator it =
+ string_list_.begin(); it != string_list_.end(); ++it)
{
if (!s.empty()) {
s.push_back(' ');
}
s.push_back('"');
- s.insert(s.end(), (*it).begin() + 1, (*it).end());
+ s.append(charStringToString(*it));
s.push_back('"');
}
@@ -189,7 +207,7 @@ public:
OutputBuffer this_buffer(0);
toWire(this_buffer);
uint8_t const* const this_data = (uint8_t const*)this_buffer.getData();
- size_t this_len = this_buffer.getLength();
+ const size_t this_len = this_buffer.getLength();
OutputBuffer other_buffer(0);
other.toWire(other_buffer);
@@ -214,11 +232,14 @@ private:
std::vector<std::vector<uint8_t> > string_list_;
};
-// END_RDATA_NAMESPACE
-// END_ISC_NAMESPACE
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
#endif // TXT_LIKE_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
index 12f034c..319ec7d 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.cc
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -14,55 +14,105 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
-#include <dns/character_string.h>
-#include <util/strutil.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
using namespace std;
-using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl {
+public:
+ HINFOImpl(const std::string& hinfo_str) {
+ std::istringstream ss(hinfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseHINFOData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid HINFO text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+ << hinfo_str << "': " << ex.what());
+ }
+ }
-HINFO::HINFO(const std::string& hinfo_str) {
- string::const_iterator input_iterator = hinfo_str.begin();
- cpu_ = getNextCharacterString(hinfo_str, input_iterator);
+ HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "HINFO RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
- skipLeftSpaces(hinfo_str, input_iterator);
+ HINFOImpl(MasterLexer& lexer)
+ {
+ parseHINFOData(lexer);
+ }
- os_ = getNextCharacterString(hinfo_str, input_iterator);
-}
+private:
+ void
+ parseHINFOData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), cpu);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), os);
+ }
-HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) {
- cpu_ = getNextCharacterString(buffer, rdata_len);
- os_ = getNextCharacterString(buffer, rdata_len);
-}
+public:
+ detail::CharString cpu;
+ detail::CharString os;
+};
+
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
+
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new HINFOImpl(buffer, rdata_len))
+{}
HINFO::HINFO(const HINFO& source):
- Rdata(), cpu_(source.cpu_), os_(source.os_)
+ Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
{
+ impl_.reset(new HINFOImpl(*source.impl_));
+ return (*this);
+}
+
+HINFO::~HINFO() {
}
std::string
HINFO::toText() const {
string result;
result += "\"";
- result += cpu_;
+ result += detail::charStringToString(impl_->cpu);
result += "\" \"";
- result += os_;
+ result += detail::charStringToString(impl_->os);
result += "\"";
return (result);
}
@@ -81,48 +131,28 @@ int
HINFO::compare(const Rdata& other) const {
const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
- if (cpu_ < other_hinfo.cpu_) {
- return (-1);
- } else if (cpu_ > other_hinfo.cpu_) {
- return (1);
- }
-
- if (os_ < other_hinfo.os_) {
- return (-1);
- } else if (os_ > other_hinfo.os_) {
- return (1);
+ const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+ if (cmp != 0) {
+ return (cmp);
}
-
- return (0);
+ return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
}
-const std::string&
+const std::string
HINFO::getCPU() const {
- return (cpu_);
+ return (detail::charStringToString(impl_->cpu));
}
-const std::string&
+const std::string
HINFO::getOS() const {
- return (os_);
+ return (detail::charStringToString(impl_->os));
}
+template <typename T>
void
-HINFO::skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format, field is missing.");
- }
-
- if (!isspace(*input_iterator)) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format, fields are not separated by space.");
- }
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
+HINFO::toWireHelper(T& outputer) const {
+ outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+ outputer.writeData(&impl_->os[0], impl_->os.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/hinfo_13.h b/src/lib/dns/rdata/generic/hinfo_13.h
index 8513419..60404c9 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.h
+++ b/src/lib/dns/rdata/generic/hinfo_13.h
@@ -17,6 +17,9 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -28,6 +31,8 @@
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl;
+
/// \brief \c HINFO class represents the HINFO rdata defined in
/// RFC1034, RFC1035
///
@@ -40,31 +45,21 @@ public:
// END_COMMON_MEMBERS
// HINFO specific methods
- const std::string& getCPU() const;
- const std::string& getOS() const;
+ ~HINFO();
-private:
- /// Skip the left whitespaces of the input string
- ///
- /// \param input_str The input string
- /// \param input_iterator From which the skipping started
- void skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator);
+ HINFO& operator=(const HINFO&);
+ const std::string getCPU() const;
+ const std::string getOS() const;
+
+private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint8(cpu_.size());
- outputer.writeData(cpu_.c_str(), cpu_.size());
-
- outputer.writeUint8(os_.size());
- outputer.writeData(os_.c_str(), os_.size());
- }
+ void toWireHelper(T& outputer) const;
- std::string cpu_;
- std::string os_;
+ boost::scoped_ptr<HINFOImpl> impl_;
};
diff --git a/src/lib/dns/rdata/generic/naptr_35.cc b/src/lib/dns/rdata/generic/naptr_35.cc
index 0958eff..78bf472 100644
--- a/src/lib/dns/rdata/generic/naptr_35.cc
+++ b/src/lib/dns/rdata/generic/naptr_35.cc
@@ -14,119 +14,147 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/character_string.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-namespace {
-/// Skip the left whitespaces of the input string
-///
-/// \param input_str The input string
-/// \param input_iterator From which the skipping started
-void
-skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, field is missing.");
+class NAPTRImpl {
+public:
+ NAPTRImpl() : replacement(".") {}
+
+ NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+ if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: insufficient length ");
+ }
+ order = buffer.readUint16();
+ preference = buffer.readUint16();
+ rdata_len -= 4;
+
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+ replacement = Name(buffer);
+ if (rdata_len < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: missing replacement name");
+ }
+ rdata_len -= replacement.getLength();
+
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "NAPTR RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
}
- if (!isspace(*input_iterator)) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, fields are not separated by space.");
+ NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+ std::istringstream ss(naptr_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseNAPTRData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+ << naptr_str << "': " << ex.what());
+ }
}
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
-}
-} // Anonymous namespace
+ NAPTRImpl(MasterLexer& lexer) : replacement(".")
+ {
+ parseNAPTRData(lexer);
+ }
-NAPTR::NAPTR(InputBuffer& buffer, size_t len):
- replacement_(".")
-{
- order_ = buffer.readUint16();
- preference_ = buffer.readUint16();
+private:
+ void
+ parseNAPTRData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: order out of range: "
+ << token.getNumber());
+ }
+ order = token.getNumber();
+ token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: preference out of range: "
+ << token.getNumber());
+ }
+ preference = token.getNumber();
+
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), flags);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), services);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), regexp);
+
+ token = lexer.getNextToken(MasterToken::STRING);
+ replacement = Name(token.getString());
+ }
- flags_ = getNextCharacterString(buffer, len);
- services_ = getNextCharacterString(buffer, len);
- regexp_ = getNextCharacterString(buffer, len);
- replacement_ = Name(buffer);
-}
-NAPTR::NAPTR(const std::string& naptr_str):
- replacement_(".")
-{
- istringstream iss(naptr_str);
+public:
uint16_t order;
uint16_t preference;
-
- iss >> order >> preference;
-
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NAPTR text format");
- }
-
- order_ = order;
- preference_ = preference;
-
- string::const_iterator input_iterator = naptr_str.begin() + iss.tellg();
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- flags_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- services_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- regexp_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- if (input_iterator < naptr_str.end()) {
- string replacementStr(input_iterator, naptr_str.end());
-
- replacement_ = Name(replacementStr);
- } else {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, replacement field is missing");
- }
+ detail::CharString flags;
+ detail::CharString services;
+ detail::CharString regexp;
+ Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) : Rdata(),
+ impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+ impl_.reset(new NAPTRImpl(*source.impl_));
+ return (*this);
}
-NAPTR::NAPTR(const NAPTR& naptr):
- Rdata(), order_(naptr.order_), preference_(naptr.preference_),
- flags_(naptr.flags_), services_(naptr.services_), regexp_(naptr.regexp_),
- replacement_(naptr.replacement_)
-{
+NAPTR::~NAPTR() {
}
void
NAPTR::toWire(OutputBuffer& buffer) const {
toWireHelper(buffer);
- replacement_.toWire(buffer);
-
+ impl_->replacement.toWire(buffer);
}
void
@@ -134,23 +162,23 @@ NAPTR::toWire(AbstractMessageRenderer& renderer) const {
toWireHelper(renderer);
// Type NAPTR is not "well-known", and name compression must be disabled
// per RFC3597.
- renderer.writeName(replacement_, false);
+ renderer.writeName(impl_->replacement, false);
}
string
NAPTR::toText() const {
string result;
- result += lexical_cast<string>(order_);
+ result += lexical_cast<string>(impl_->order);
result += " ";
- result += lexical_cast<string>(preference_);
+ result += lexical_cast<string>(impl_->preference);
result += " \"";
- result += flags_;
+ result += detail::charStringToString(impl_->flags);
result += "\" \"";
- result += services_;
+ result += detail::charStringToString(impl_->services);
result += "\" \"";
- result += regexp_;
+ result += detail::charStringToString(impl_->regexp);
result += "\" ";
- result += replacement_.toText();
+ result += impl_->replacement.toText();
return (result);
}
@@ -158,67 +186,78 @@ int
NAPTR::compare(const Rdata& other) const {
const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
- if (order_ < other_naptr.order_) {
+ if (impl_->order < other_naptr.impl_->order) {
return (-1);
- } else if (order_ > other_naptr.order_) {
+ } else if (impl_->order > other_naptr.impl_->order) {
return (1);
}
- if (preference_ < other_naptr.preference_) {
+ if (impl_->preference < other_naptr.impl_->preference) {
return (-1);
- } else if (preference_ > other_naptr.preference_) {
+ } else if (impl_->preference > other_naptr.impl_->preference) {
return (1);
}
- if (flags_ < other_naptr.flags_) {
- return (-1);
- } else if (flags_ > other_naptr.flags_) {
- return (1);
+ const int fcmp = detail::compareCharStrings(impl_->flags,
+ other_naptr.impl_->flags);
+ if (fcmp != 0) {
+ return (fcmp);
}
- if (services_ < other_naptr.services_) {
- return (-1);
- } else if (services_ > other_naptr.services_) {
- return (1);
+ const int scmp = detail::compareCharStrings(impl_->services,
+ other_naptr.impl_->services);
+ if (scmp != 0) {
+ return (scmp);
}
- if (regexp_ < other_naptr.regexp_) {
- return (-1);
- } else if (regexp_ > other_naptr.regexp_) {
- return (1);
+ const int rcmp = detail::compareCharStrings(impl_->regexp,
+ other_naptr.impl_->regexp);
+ if (rcmp != 0) {
+ return (rcmp);
}
- return (compareNames(replacement_, other_naptr.replacement_));
+ return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
}
uint16_t
NAPTR::getOrder() const {
- return (order_);
+ return (impl_->order);
}
uint16_t
NAPTR::getPreference() const {
- return (preference_);
+ return (impl_->preference);
}
-const std::string&
+const std::string
NAPTR::getFlags() const {
- return (flags_);
+ return (detail::charStringToString(impl_->flags));
}
-const std::string&
+const std::string
NAPTR::getServices() const {
- return (services_);
+ return (detail::charStringToString(impl_->services));
}
-const std::string&
+const std::string
NAPTR::getRegexp() const {
- return (regexp_);
+ return (detail::charStringToString(impl_->regexp));
}
const Name&
NAPTR::getReplacement() const {
- return (replacement_);
+ return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+ outputer.writeUint16(impl_->order);
+ outputer.writeUint16(impl_->preference);
+
+ outputer.writeData(&impl_->flags[0], impl_->flags.size());
+ outputer.writeData(&impl_->services[0], impl_->services.size());
+ outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/naptr_35.h b/src/lib/dns/rdata/generic/naptr_35.h
index c82ef87..a2e2cae 100644
--- a/src/lib/dns/rdata/generic/naptr_35.h
+++ b/src/lib/dns/rdata/generic/naptr_35.h
@@ -16,6 +16,8 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -27,6 +29,8 @@
// BEGIN_RDATA_NAMESPACE
+class NAPTRImpl;
+
/// \brief \c NAPTR class represents the NAPTR rdata defined in
/// RFC2915, RFC2168 and RFC3403
///
@@ -39,37 +43,24 @@ public:
// END_COMMON_MEMBERS
// NAPTR specific methods
+ ~NAPTR();
+
+ NAPTR& operator=(const NAPTR& source);
+
uint16_t getOrder() const;
uint16_t getPreference() const;
- const std::string& getFlags() const;
- const std::string& getServices() const;
- const std::string& getRegexp() const;
+ const std::string getFlags() const;
+ const std::string getServices() const;
+ const std::string getRegexp() const;
const Name& getReplacement() const;
private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint16(order_);
- outputer.writeUint16(preference_);
-
- outputer.writeUint8(flags_.size());
- outputer.writeData(flags_.c_str(), flags_.size());
-
- outputer.writeUint8(services_.size());
- outputer.writeData(services_.c_str(), services_.size());
-
- outputer.writeUint8(regexp_.size());
- outputer.writeData(regexp_.c_str(), regexp_.size());
- }
+ void toWireHelper(T& outputer) const;
- uint16_t order_;
- uint16_t preference_;
- std::string flags_;
- std::string services_;
- std::string regexp_;
- Name replacement_;
+ boost::scoped_ptr<NAPTRImpl> impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index f2b9627..56eb7a2 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -14,21 +14,29 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -41,35 +49,85 @@ SOA::SOA(InputBuffer& buffer, size_t) :
buffer.readData(numdata_, sizeof(numdata_));
}
+namespace {
+void
+fillParameters(MasterLexer& lexer, uint8_t numdata[20]) {
+ // Copy serial, refresh, retry, expire, minimum. We accept the extended
+ // TTL-compatible style for the latter four.
+ OutputBuffer buffer(20);
+ buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ for (int i = 0; i < 4; ++i) {
+ buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING).
+ getString()).getValue());
+ }
+ memcpy(numdata, buffer.getData(), buffer.getLength());
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SOA RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The MNAME and RNAME must be absolute since there's no parameter that
+/// specifies the origin name; if these are not absolute, \c MissingNameOrigin
+/// exception will be thrown. These must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
SOA::SOA(const std::string& soastr) :
- mname_("."), rname_(".") // quick hack workaround
+ // Fill in dummy name and replace them soon below.
+ mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME())
{
- istringstream iss(soastr);
- string token;
-
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA MNAME");
- }
- mname_ = Name(token);
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA RNAME");
+ try {
+ std::istringstream ss(soastr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mname_ = createNameFromLexer(lexer, NULL);
+ rname_ = createNameFromLexer(lexer, NULL);
+ fillParameters(lexer, numdata_);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SOA: "
+ << soastr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SOA from '" <<
+ soastr << "': " << ex.what());
}
- rname_ = Name(token);
+}
- uint32_t serial, refresh, retry, expire, minimum;
- iss >> serial >> refresh >> retry >> expire >> minimum;
- if (iss.rdstate() != ios::eofbit) {
- isc_throw(InvalidRdataText, "Invalid SOA format");
- }
- OutputBuffer buffer(20);
- buffer.writeUint32(serial);
- buffer.writeUint32(refresh);
- buffer.writeUint32(retry);
- buffer.writeUint32(expire);
- buffer.writeUint32(minimum);
- memcpy(numdata_, buffer.getData(), buffer.getLength());
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SOA RDATA. The MNAME and RNAME fields can be non absolute if
+/// \c origin is non NULL, in which case \c origin is used to make them
+/// absolute. These must not be represented as a quoted string.
+///
+/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid
+/// decimal representation of an unsigned 32-bit integer or other
+/// valid textual representation of \c RRTTL such as "1H" (which means 3600).
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of MNAME and RNAME when
+/// they are non absolute.
+SOA::SOA(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mname_(createNameFromLexer(lexer, origin)),
+ rname_(createNameFromLexer(lexer, origin))
+{
+ fillParameters(lexer, numdata_);
}
SOA::SOA(const Name& mname, const Name& rname, uint32_t serial,
@@ -112,6 +170,16 @@ SOA::getSerial() const {
return (Serial(b.readUint32()));
}
+uint32_t
+SOA::getMinimum() const {
+ // Make sure the buffer access is safe.
+ BOOST_STATIC_ASSERT(sizeof(numdata_) ==
+ sizeof(uint32_t) * 4 + sizeof(uint32_t));
+
+ InputBuffer b(&numdata_[sizeof(uint32_t) * 4], sizeof(uint32_t));
+ return (b.readUint32());
+}
+
string
SOA::toText() const {
InputBuffer b(numdata_, sizeof(numdata_));
diff --git a/src/lib/dns/rdata/generic/soa_6.h b/src/lib/dns/rdata/generic/soa_6.h
index 2c180b2..d736666 100644
--- a/src/lib/dns/rdata/generic/soa_6.h
+++ b/src/lib/dns/rdata/generic/soa_6.h
@@ -35,8 +35,12 @@ public:
SOA(const Name& mname, const Name& rname, uint32_t serial,
uint32_t refresh, uint32_t retry, uint32_t expire,
uint32_t minimum);
+
/// \brief Returns the serial stored in the SOA.
Serial getSerial() const;
+
+ /// brief Returns the minimum TTL field value of the SOA.
+ uint32_t getMinimum() const;
private:
/// Note: this is a prototype version; we may reconsider
/// this representation later.
diff --git a/src/lib/dns/rdata/generic/spf_99.cc b/src/lib/dns/rdata/generic/spf_99.cc
index aa3e4a1..4bf24e9 100644
--- a/src/lib/dns/rdata/generic/spf_99.cc
+++ b/src/lib/dns/rdata/generic/spf_99.cc
@@ -24,18 +24,17 @@
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class. The semantics of the class is provided by
+/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
+#include <dns/rdata/generic/detail/txt_like.h>
+
using namespace std;
using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-/// This class implements the basic interfaces inherited from the abstract
-/// \c rdata::Rdata class. The semantics of the class is provided by
-/// a copy of instantiated TXTLikeImpl class common to both TXT and SPF.
-
-#include <dns/rdata/generic/detail/txt_like.h>
-
/// \brief The assignment operator
///
/// It internally allocates a resource, and if it fails a corresponding
@@ -67,6 +66,21 @@ SPF::SPF(InputBuffer& buffer, size_t rdata_len) :
impl_(new SPFImpl(buffer, rdata_len))
{}
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+SPF::SPF(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(new SPFImpl(lexer))
+{}
+
/// \brief Constructor from string.
///
/// It internally allocates a resource, and if it fails a corresponding
diff --git a/src/lib/dns/rdata/generic/spf_99.h b/src/lib/dns/rdata/generic/spf_99.h
index 04ac99b..eece47b 100644
--- a/src/lib/dns/rdata/generic/spf_99.h
+++ b/src/lib/dns/rdata/generic/spf_99.h
@@ -28,7 +28,9 @@
// BEGIN_RDATA_NAMESPACE
+namespace detail {
template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
/// \brief \c rdata::SPF class represents the SPF RDATA as defined %in
/// RFC4408.
@@ -65,7 +67,7 @@ public:
const std::vector<std::vector<uint8_t> >& getString() const;
private:
- typedef TXTLikeImpl<SPF, 99> SPFImpl;
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<SPF, 99> SPFImpl;
SPFImpl* impl_;
};
diff --git a/src/lib/dns/rdata/generic/txt_16.cc b/src/lib/dns/rdata/generic/txt_16.cc
index 418bc05..1bd2eb1 100644
--- a/src/lib/dns/rdata/generic/txt_16.cc
+++ b/src/lib/dns/rdata/generic/txt_16.cc
@@ -23,6 +23,7 @@
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/txt_like.h>
using namespace std;
using namespace isc::util;
@@ -30,8 +31,6 @@ using namespace isc::util;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-#include <dns/rdata/generic/detail/txt_like.h>
-
TXT&
TXT::operator=(const TXT& source) {
if (impl_ == source.impl_) {
@@ -53,6 +52,21 @@ TXT::TXT(InputBuffer& buffer, size_t rdata_len) :
impl_(new TXTImpl(buffer, rdata_len))
{}
+/// \brief Constructor using the master lexer.
+///
+/// This implementation only uses the \c lexer parameters; others are
+/// ignored.
+///
+/// \throw CharStringTooLong the parameter string length exceeds maximum.
+/// \throw InvalidRdataText the method cannot process the parameter data
+///
+/// \param lexer A \c MasterLexer object parsing a master file for this
+/// RDATA.
+TXT::TXT(MasterLexer& lexer, const Name*, MasterLoader::Options,
+ MasterLoaderCallbacks&) :
+ impl_(new TXTImpl(lexer))
+{}
+
TXT::TXT(const std::string& txtstr) :
impl_(new TXTImpl(txtstr))
{}
diff --git a/src/lib/dns/rdata/generic/txt_16.h b/src/lib/dns/rdata/generic/txt_16.h
index d99d69b..e434085 100644
--- a/src/lib/dns/rdata/generic/txt_16.h
+++ b/src/lib/dns/rdata/generic/txt_16.h
@@ -28,7 +28,9 @@
// BEGIN_RDATA_NAMESPACE
+namespace detail {
template<class Type, uint16_t typeCode> class TXTLikeImpl;
+}
class TXT : public Rdata {
public:
@@ -39,7 +41,7 @@ public:
~TXT();
private:
- typedef TXTLikeImpl<TXT, 16> TXTImpl;
+ typedef isc::dns::rdata::generic::detail::TXTLikeImpl<TXT, 16> TXTImpl;
TXTImpl* impl_;
};
diff --git a/src/lib/dns/rdata/in_1/aaaa_28.cc b/src/lib/dns/rdata/in_1/aaaa_28.cc
index ce49a04..0466f1a 100644
--- a/src/lib/dns/rdata/in_1/aaaa_28.cc
+++ b/src/lib/dns/rdata/in_1/aaaa_28.cc
@@ -12,6 +12,15 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <exceptions/exceptions.h>
+#include <util/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/messagerenderer.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+
#include <stdint.h>
#include <string.h>
@@ -20,14 +29,6 @@
#include <arpa/inet.h> // XXX: for inet_pton/ntop(), not exist in C++ standards
#include <sys/socket.h> // for AF_INET/AF_INET6
-#include <exceptions/exceptions.h>
-
-#include <util/buffer.h>
-#include <dns/exceptions.h>
-#include <dns/messagerenderer.h>
-#include <dns/rdata.h>
-#include <dns/rdataclass.h>
-
using namespace std;
using namespace isc::util;
@@ -42,6 +43,16 @@ AAAA::AAAA(const std::string& addrstr) {
}
}
+AAAA::AAAA(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&)
+{
+ const MasterToken& token = lexer.getNextToken(MasterToken::STRING);
+ if (inet_pton(AF_INET6, token.getStringRegion().beg, &addr_) != 1) {
+ isc_throw(InvalidRdataText, "Failed to convert '"
+ << token.getString() << "' to IN/AAAA RDATA");
+ }
+}
+
AAAA::AAAA(InputBuffer& buffer, size_t rdata_len) {
if (rdata_len != sizeof(addr_)) {
isc_throw(DNSMessageFORMERR,
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
index ee1097e..6486e6a 100644
--- a/src/lib/dns/rdata/template.cc
+++ b/src/lib/dns/rdata/template.cc
@@ -34,6 +34,11 @@ using namespace isc::util;
// If you added member functions specific to this derived class, you'll need
// to implement them here, of course.
+MyType::MyType(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options, MasterLoaderCallbacks& callbacks)
+{
+}
+
MyType::MyType(const string& type_str) {
}
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index 70d6b78..1ff4163 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -22,6 +22,8 @@
#include <exceptions/exceptions.h>
+#include <boost/optional.hpp>
+
namespace isc {
namespace util {
class InputBuffer;
@@ -33,6 +35,16 @@ namespace dns {
// forward declarations
class AbstractMessageRenderer;
+class RRClass; // forward declaration to define MaybeRRClass.
+
+/// \brief A shortcut for a compound type to represent RRClass-or-not.
+///
+/// A value of this type can be interpreted in a boolean context, whose
+/// value is \c true if and only if it contains a valid RRClass object.
+/// And, if it contains a valid RRClass object, its value is accessible
+/// using \c operator*, just like a bare pointer to \c RRClass.
+typedef boost::optional<RRClass> MaybeRRClass;
+
///
/// \brief A standard DNS module exception that is thrown if an RRClass object
/// is being constructed from an unrecognized string.
@@ -123,8 +135,8 @@ public:
/// If the given string is not recognized as a valid representation of
/// an RR class, an exception of class \c InvalidRRClass will be thrown.
///
- /// \param classstr A string representation of the \c RRClass
- explicit RRClass(const std::string& classstr);
+ /// \param class_str A string representation of the \c RRClass
+ explicit RRClass(const std::string& class_str);
/// Constructor from wire-format data.
///
/// The \c buffer parameter normally stores a complete DNS message
@@ -136,6 +148,45 @@ public:
///
/// \param buffer A buffer storing the wire format data.
explicit RRClass(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRClass from text.
+ ///
+ /// This static method is similar to the constructor that takes a
+ /// string object, but works as a factory and reports parsing
+ /// failure in the form of the return value. Normally the
+ /// constructor version should suffice, but in some cases the caller
+ /// may have to expect mixture of valid and invalid input, and may
+ /// want to minimize the overhead of possible exception handling.
+ /// This version is provided for such purpose.
+ ///
+ /// For the format of the \c class_str argument, see the
+ /// <code>RRClass(const std::string&)</code> constructor.
+ ///
+ /// If the given text represents a valid RRClass, it returns a
+ /// \c MaybeRRClass object that stores a corresponding \c RRClass
+ /// object, which is accessible via \c operator*(). In this case
+ /// the returned object will be interpreted as \c true in a boolean
+ /// context. If the given text does not represent a valid RRClass,
+ /// it returns a \c MaybeRRClass object which is interpreted as
+ /// \c false in a boolean context.
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR class. For
+ /// this reason this function intentionally omits the capability of
+ /// delivering a detailed reason for the parse failure, such as in the
+ /// \c want() string when exception is thrown from the constructor
+ /// (it will internally require a creation of string object, which
+ /// is relatively expensive). If such detailed information is
+ /// necessary, the constructor version should be used to catch the
+ /// resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRClass exception.
+ ///
+ /// \param class_str A string representation of the \c RRClass.
+ /// \return A MaybeRRClass object either storing an RRClass object
+ /// for the given text or a \c false value.
+ static MaybeRRClass createFromText(const std::string& class_str);
+
///
/// We use the default copy constructor intentionally.
//@}
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index ac5823c..76a2e73 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -30,8 +30,11 @@ using namespace isc::util;
namespace isc {
namespace dns {
-RRClass::RRClass(const std::string& classstr) {
- classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
+RRClass::RRClass(const std::string& class_str) {
+ if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode_)) {
+ isc_throw(InvalidRRClass,
+ "Unrecognized RR class string: " + class_str);
+ }
}
RRClass::RRClass(InputBuffer& buffer) {
@@ -56,6 +59,16 @@ RRClass::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeUint16(classcode_);
}
+MaybeRRClass
+RRClass::createFromText(const string& class_str) {
+ uint16_t class_code;
+ if (RRParamRegistry::getRegistry().textToClassCode(class_str,
+ class_code)) {
+ return (MaybeRRClass(class_code));
+ }
+ return (MaybeRRClass());
+}
+
ostream&
operator<<(ostream& os, const RRClass& rrclass) {
os << rrclass.toText();
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
new file mode 100644
index 0000000..153de04
--- /dev/null
+++ b/src/lib/dns/rrcollator.cc
@@ -0,0 +1,110 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+// include this first to check the header is self-contained.
+#include <dns/rrcollator.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+
+#include <boost/bind.hpp>
+
+#include <algorithm>
+
+namespace isc {
+namespace dns {
+using namespace rdata;
+
+class RRCollator::Impl {
+public:
+ Impl(const AddRRsetCallback& callback) : callback_(callback) {}
+
+ void addRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const RdataPtr& rdata);
+
+ RRsetPtr current_rrset_;
+ const AddRRsetCallback callback_;
+};
+
+namespace {
+inline bool
+isSameType(RRType type1, const ConstRdataPtr& rdata1,
+ const ConstRRsetPtr& rrset)
+{
+ if (type1 != rrset->getType()) {
+ return (false);
+ }
+ if (type1 == RRType::RRSIG()) {
+ RdataIteratorPtr rit = rrset->getRdataIterator();
+ return (dynamic_cast<const generic::RRSIG&>(*rdata1).typeCovered()
+ == dynamic_cast<const generic::RRSIG&>(
+ rit->getCurrent()).typeCovered());
+ }
+ return (true);
+}
+}
+
+void
+RRCollator::Impl::addRR(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const RdataPtr& rdata)
+{
+ if (current_rrset_ && (!isSameType(rrtype, rdata, current_rrset_) ||
+ current_rrset_->getClass() != rrclass ||
+ current_rrset_->getName() != name)) {
+ callback_(current_rrset_);
+ current_rrset_.reset();
+ }
+
+ if (!current_rrset_) {
+ current_rrset_ = RRsetPtr(new RRset(name, rrclass, rrtype, rrttl));
+ } else if (current_rrset_->getTTL() != rrttl) {
+ // RRs with different TTLs are given. Smaller TTL should win.
+ current_rrset_->setTTL(std::min(current_rrset_->getTTL(), rrttl));
+ }
+ current_rrset_->addRdata(rdata);
+}
+
+RRCollator::RRCollator(const AddRRsetCallback& callback) :
+ impl_(new Impl(callback))
+{}
+
+RRCollator::~RRCollator() {
+ delete impl_;
+}
+
+AddRRCallback
+RRCollator::getCallback() {
+ return (boost::bind(&RRCollator::Impl::addRR, this->impl_,
+ _1, _2, _3, _4, _5));
+}
+
+void
+RRCollator::flush() {
+ if (impl_->current_rrset_) {
+ impl_->callback_(impl_->current_rrset_);
+ impl_->current_rrset_.reset();
+ }
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/rrcollator.h b/src/lib/dns/rrcollator.h
new file mode 100644
index 0000000..3a9e0aa
--- /dev/null
+++ b/src/lib/dns/rrcollator.h
@@ -0,0 +1,133 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRCOLLATOR_H
+#define RRCOLLATOR_H 1
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrset.h>
+
+#include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+
+namespace isc {
+namespace dns {
+
+/// \brief A converter from a stream of RRs to a stream of collated RRsets
+///
+/// This class is mainly intended to be a helper used as an adaptor for
+/// user applications of the \c MasterLoader class; it works as a callback
+/// for \c MasterLoader, buffers given RRs from the loader, collating
+/// consecutive RRs that belong to the same RRset (ones having the same
+/// owner name, RR type and class), and produces a stream of RRsets through
+/// its own callback. RRSIGs are also separated if their type covered fields
+/// have different values even if the owner name and RR class are the same.
+///
+/// It also "normalizes" TTLs of the RR; if collated RRs have different TTLs,
+/// this class guarantees that the TTL of the resulting RRsets has the
+/// smallest TTL among them.
+///
+/// The conversion will be useful for applications of \c MasterLoader because
+/// many of this library have interfaces that take an RRset object (or
+/// a pointer to it). Note, however, that this class doesn't guarantee that
+/// all RRs that would belong to the same RRset are collated into the same
+/// single RRset. In fact, it can only collate RRs that are consecutive
+/// in the original stream; once it encounters an RR of a different RRset,
+/// any subsequent RRs of the previous RRset will form a separate RRset object.
+///
+/// This class is non-copyable; it's partially for the convenience of internal
+/// implementation details, but it actually doesn't make sense to copy
+/// an object of this class, if not harmful, for the intended usage of
+/// the class.
+class RRCollator : boost::noncopyable {
+public:
+ /// \brief Callback functor type for \c RRCollator.
+ ///
+ /// This type of callback is given to an \c RRCollator object on its
+ /// construction, and will be called for each collated RRset built in
+ /// the \c RRCollator.
+ ///
+ /// \param rrset The collated RRset.
+ typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
+
+ /// \brief Constructor.
+ ///
+ /// \throw std::bad_alloc Internal memory allocation fails. This should
+ /// be very rare.
+ ///
+ /// \param callback The callback functor to be called for each collated
+ /// RRset.
+ RRCollator(const AddRRsetCallback& callback);
+
+ /// \brief Destructor.
+ ///
+ /// It only performs trivial internal cleanup. In particular, even if
+ /// it still has a buffered RRset it will be simply discarded. This is
+ /// because the given callback could throw an exception, and it's
+ /// impossible to predict how this class is used (to see if it's a very
+ /// rare case where propagating an exception from a destructor is
+ /// justified). Instead, the application needs to make sure that
+ /// \c flush() is called before the object of this class is destroyed.
+ ///
+ /// \throw None
+ ~RRCollator();
+
+ /// \brief Call the callback on the remaining RRset, if any.
+ ///
+ /// This method is expected to be called that it's supposed all RRs have
+ /// been passed to this class object. Since there is no explicit
+ /// indicator of the end of the stream, the user of this class needs to
+ /// explicitly call this method to call the callback for the last buffered
+ /// RRset (see also the destructor's description).
+ ///
+ /// If there is no buffered RRset, this method does nothing. It can happen
+ /// if it's called without receiving any RRs, or called more than once.
+ ///
+ /// It propagates any exception thrown from the callback; otherwise it
+ /// doesn't throw anything.
+ void flush();
+
+ /// \brief Return \c MasterLoader compatible callback.
+ ///
+ /// This method returns a functor in the form of \c AddRRCallback
+ /// that works as an adaptor between \c MasterLoader and an application
+ /// that needs to get a stream of RRsets. When the returned callback
+ /// is called, this \c RRCollator object accepts the corresponding RR,
+ /// and collates it with other RRs of the same RRset if necessary.
+ /// Every time the \c RRCollator object encounters an RR of a different
+ /// RRset, it calls the callback passed to the constructor with the RRset
+ /// built so far.
+ ///
+ /// Like \c flush(), this \c AddRRCallback functor propagates any exception
+ /// thrown from the callback.
+ ///
+ /// This method is expected to be called only once for a given
+ /// \c RRCollator object. It doesn't prohibit duplicate calls, but
+ /// returned functor objects internally refer to the same \c RRCollator
+ /// object, and calling the both callbacks randomly will just cause
+ /// confusion.
+ AddRRCallback getCallback();
+
+private:
+ class Impl;
+ Impl* impl_;
+};
+
+} // namespace dns
+} // namespace isc
+#endif // RRCOLLATOR_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index f7f3a1a..5960759 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -34,13 +34,42 @@
#include <dns/rdataclass.h>
using namespace std;
-using namespace boost;
using namespace isc::util;
-using namespace isc::dns::rdata;
+using namespace isc::dns::rdata;
namespace isc {
namespace dns {
+
+namespace rdata {
+
+RdataPtr
+AbstractRdataFactory::create(MasterLexer& lexer, const Name*,
+ MasterLoader::Options,
+ MasterLoaderCallbacks&) const
+{
+ std::string s;
+
+ while (true) {
+ const MasterToken& token = lexer.getNextToken();
+ if ((token.getType() == MasterToken::END_OF_FILE) ||
+ (token.getType() == MasterToken::END_OF_LINE)) {
+ lexer.ungetToken(); // let the upper layer handle the end-of token
+ break;
+ }
+
+ if (!s.empty()) {
+ s += " ";
+ }
+
+ s += token.getString();
+ }
+
+ return (create(s));
+}
+
+} // end of namespace isc::dns::rdata
+
namespace {
///
/// The following function and class are a helper to define case-insensitive
@@ -161,8 +190,10 @@ typedef map<RRTypeClass, RdataFactoryPtr> RdataFactoryMap;
typedef map<RRType, RdataFactoryPtr> GenericRdataFactoryMap;
template <typename T>
-class RdataFactory : public AbstractRdataFactory {
+class OldRdataFactory : public AbstractRdataFactory {
public:
+ using AbstractRdataFactory::create;
+
virtual RdataPtr create(const string& rdata_str) const
{
return (RdataPtr(new T(rdata_str)));
@@ -179,6 +210,18 @@ public:
}
};
+template <typename T>
+class RdataFactory : public OldRdataFactory<T> {
+public:
+ using OldRdataFactory<T>::create;
+
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const {
+ return (RdataPtr(new T(lexer, origin, options, callbacks)));
+ }
+};
+
///
/// \brief The \c RRParamRegistryImpl class is the actual implementation of
/// \c RRParamRegistry.
@@ -305,7 +348,7 @@ namespace {
/// This could be simplified using strncasecmp(), but unfortunately it's not
/// included in <cstring>. To be as much as portable within the C++ standard
/// we take the "in house" approach here.
-///
+///
bool CICharEqual(char c1, char c2) {
return (tolower(static_cast<unsigned char>(c1)) ==
tolower(static_cast<unsigned char>(c2)));
@@ -378,14 +421,15 @@ removeParam(uint16_t code, MC& codemap, MS& stringmap) {
return (false);
}
-template <typename PT, typename MS, typename ET>
-inline uint16_t
-textToCode(const string& code_str, MS& stringmap) {
+template <typename PT, typename MS>
+inline bool
+textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
typename MS::const_iterator found;
found = stringmap.find(code_str);
if (found != stringmap.end()) {
- return (found->second->code_);
+ ret_code = found->second->code_;
+ return (true);
}
size_t l = code_str.size();
@@ -398,10 +442,12 @@ textToCode(const string& code_str, MS& stringmap) {
l - PT::UNKNOWN_PREFIXLEN()));
iss >> dec >> code;
if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
- return (code);
+ ret_code = code;
+ return (true);
}
}
- isc_throw(ET, "Unrecognized RR parameter string: " + code_str);
+
+ return (false);
}
template <typename PT, typename MC>
@@ -432,10 +478,12 @@ RRParamRegistry::removeType(uint16_t code) {
impl_->str2typemap));
}
-uint16_t
-RRParamRegistry::textToTypeCode(const string& type_string) const {
- return (textToCode<RRTypeParam, StrRRTypeMap,
- InvalidRRType>(type_string, impl_->str2typemap));
+bool
+RRParamRegistry::textToTypeCode(const string& type_string,
+ uint16_t& type_code) const
+{
+ return (textToCode<RRTypeParam, StrRRTypeMap>
+ (type_string, impl_->str2typemap, type_code));
}
string
@@ -456,10 +504,12 @@ RRParamRegistry::removeClass(uint16_t code) {
impl_->str2classmap));
}
-uint16_t
-RRParamRegistry::textToClassCode(const string& class_string) const {
- return (textToCode<RRClassParam, StrRRClassMap,
- InvalidRRClass>(class_string, impl_->str2classmap));
+bool
+RRParamRegistry::textToClassCode(const string& class_string,
+ uint16_t& class_code) const
+{
+ return (textToCode<RRClassParam, StrRRClassMap>
+ (class_string, impl_->str2classmap, class_code));
}
string
@@ -468,6 +518,27 @@ RRParamRegistry::codeToClassText(uint16_t code) const {
impl_->code2classmap));
}
+namespace {
+inline const AbstractRdataFactory*
+findRdataFactory(RRParamRegistryImpl* reg_impl,
+ const RRType& rrtype, const RRClass& rrclass)
+{
+ RdataFactoryMap::const_iterator found;
+ found = reg_impl->rdata_factories.find(RRTypeClass(rrtype, rrclass));
+ if (found != reg_impl->rdata_factories.end()) {
+ return (found->second.get());
+ }
+
+ GenericRdataFactoryMap::const_iterator genfound =
+ reg_impl->genericrdata_factories.find(rrtype);
+ if (genfound != reg_impl->genericrdata_factories.end()) {
+ return (genfound->second.get());
+ }
+
+ return (NULL);
+}
+}
+
RdataPtr
RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
const std::string& rdata_string)
@@ -475,16 +546,10 @@ RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
// If the text indicates that it's rdata of an "unknown" type (beginning
// with '\# n'), parse it that way. (TBD)
- RdataFactoryMap::const_iterator found;
- found = impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
- if (found != impl_->rdata_factories.end()) {
- return (found->second->create(rdata_string));
- }
-
- GenericRdataFactoryMap::const_iterator genfound =
- impl_->genericrdata_factories.find(rrtype);
- if (genfound != impl_->genericrdata_factories.end()) {
- return (genfound->second->create(rdata_string));
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_, rrtype, rrclass);
+ if (factory != NULL) {
+ return (factory->create(rdata_string));
}
return (RdataPtr(new generic::Generic(rdata_string)));
@@ -494,16 +559,10 @@ RdataPtr
RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
InputBuffer& buffer, size_t rdata_len)
{
- RdataFactoryMap::const_iterator found =
- impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
- if (found != impl_->rdata_factories.end()) {
- return (found->second->create(buffer, rdata_len));
- }
-
- GenericRdataFactoryMap::const_iterator genfound =
- impl_->genericrdata_factories.find(rrtype);
- if (genfound != impl_->genericrdata_factories.end()) {
- return (genfound->second->create(buffer, rdata_len));
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_, rrtype, rrclass);
+ if (factory != NULL) {
+ return (factory->create(buffer, rdata_len));
}
return (RdataPtr(new generic::Generic(buffer, rdata_len)));
@@ -513,20 +572,29 @@ RdataPtr
RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
const Rdata& source)
{
- RdataFactoryMap::const_iterator found =
- impl_->rdata_factories.find(RRTypeClass(rrtype, rrclass));
- if (found != impl_->rdata_factories.end()) {
- return (found->second->create(source));
- }
-
- GenericRdataFactoryMap::const_iterator genfound =
- impl_->genericrdata_factories.find(rrtype);
- if (genfound != impl_->genericrdata_factories.end()) {
- return (genfound->second->create(source));
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_, rrtype, rrclass);
+ if (factory != NULL) {
+ return (factory->create(source));
}
return (RdataPtr(new rdata::generic::Generic(
dynamic_cast<const generic::Generic&>(source))));
}
+
+RdataPtr
+RRParamRegistry::createRdata(const RRType& rrtype, const RRClass& rrclass,
+ MasterLexer& lexer, const Name* name,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks)
+{
+ const AbstractRdataFactory* factory =
+ findRdataFactory(impl_, rrtype, rrclass);
+ if (factory != NULL) {
+ return (factory->create(lexer, name, options, callbacks));
+ }
+
+ return (RdataPtr(new generic::Generic(lexer, name, options, callbacks)));
+}
}
}
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index b1ca225..bf86436 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -82,7 +82,7 @@ public:
/// \name Factory methods for polymorphic creation.
///
//@{
- ///
+
/// \brief Create RDATA from a string.
///
/// This method creates from a string an \c Rdata object of specific class
@@ -91,7 +91,7 @@ public:
/// \param rdata_str A string of textual representation of the \c Rdata.
/// \return An \c RdataPtr object pointing to the created \c Rdata object.
virtual RdataPtr create(const std::string& rdata_str) const = 0;
- ///
+
/// \brief Create RDATA from wire-format data.
///
/// This method creates from wire-format binary data an \c Rdata object
@@ -103,7 +103,7 @@ public:
/// \param rdata_len The length in buffer of the \c Rdata. In bytes.
/// \return An \c RdataPtr object pointing to the created \c Rdata object.
virtual RdataPtr create(isc::util::InputBuffer& buffer, size_t rdata_len) const = 0;
- ///
+
/// \brief Create RDATA from another \c Rdata object of the same type.
///
/// This method creates an \c Rdata object of specific class corresponding
@@ -118,6 +118,23 @@ public:
/// be copied to the created \c Rdata object.
/// \return An \c RdataPtr object pointing to the created \c Rdata object.
virtual RdataPtr create(const rdata::Rdata& source) const = 0;
+
+ /// \brief Create RDATA using MasterLexer.
+ ///
+ /// This version of the method defines the entry point of factory
+ /// of a specific RR type and class for \c RRParamRegistry::createRdata()
+ /// that uses \c MasterLexer. See its description for the expected
+ /// behavior and meaning of the parameters.
+ ///
+ /// \note Right now this is not defined as a pure virtual method and
+ /// provides the default implementation. This is an intermediate
+ /// workaround until we implement the underlying constructor for all
+ /// supported \c Rdata classes; once it's completed the workaround
+ /// default implementation should be removed and this method should become
+ /// pure virtual.
+ virtual RdataPtr create(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks) const;
//@}
};
@@ -367,16 +384,22 @@ public:
/// \brief Convert a textual representation of an RR type to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR type to the corresponding integer
- /// code. If a mapping is found, it returns the associated type code;
- /// otherwise, if the given string is in the form of "TYPEnnnn", it returns
- /// the corresponding number as the type code; otherwise, it throws an
- /// exception of class \c InvalidRRType.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR type to the corresponding
+ /// integer code. If a mapping is found, it returns true with the
+ /// associated type code in \c type_code; otherwise, if the given
+ /// string is in the form of "TYPEnnnn", it returns true with the
+ /// corresponding number as the type code in \c type_code;
+ /// otherwise, it returns false and \c type_code is untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param type_string The textual representation of the RR type.
- /// \return The RR type code for \c type_string.
- uint16_t textToTypeCode(const std::string& type_string) const;
+ /// \param type_code Returns the RR type code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToTypeCode(const std::string& type_string,
+ uint16_t& type_code) const;
/// \brief Convert type code into its textual representation.
///
@@ -398,16 +421,23 @@ public:
/// \brief Convert a textual representation of an RR class to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR class to the corresponding integer
- /// code. If a mapping is found, it returns the associated class code;
- /// otherwise, if the given string is in the form of "CLASSnnnn", it returns
- /// the corresponding number as the class code; otherwise, it throws an
- /// exception of class \c InvalidRRClass.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR class to the
+ /// corresponding integer code. If a mapping is found, it returns
+ /// true with the associated class code in \c class_code; otherwise,
+ /// if the given string is in the form of "CLASSnnnn", it returns
+ /// true with the corresponding number as the class code in
+ /// \c class_code; otherwise, it returns false and \c class_code is
+ /// untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param class_string The textual representation of the RR class.
- /// \return The RR class code for \c class_string.
- uint16_t textToClassCode(const std::string& class_string) const;
+ /// \param class_code Returns the RR class code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToClassCode(const std::string& class_string,
+ uint16_t& class_code) const;
/// \brief Convert class code into its textual representation.
///
@@ -498,6 +528,23 @@ public:
/// \c rdata::Rdata object.
rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
const rdata::Rdata& source);
+
+ /// \brief Create RDATA using MasterLexer
+ ///
+ /// This method is expected to be used as the underlying implementation
+ /// of the same signature of \c rdata::createRdata(). One main
+ /// difference is that this method is only responsible for constructing
+ /// the Rdata; it doesn't update the lexer to reach the end of line or
+ /// file or doesn't care about whether there's an extra (garbage) token
+ /// after the textual RDATA representation. Another difference is that
+ /// this method can throw on error and never returns a NULL pointer.
+ ///
+ /// For other details and parameters, see the description of
+ /// \c rdata::createRdata().
+ rdata::RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
+ MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options options,
+ MasterLoaderCallbacks& callbacks);
//@}
private:
@@ -508,6 +555,6 @@ private:
}
#endif // RRPARAMREGISTRY_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index 9c1715b..395cbdd 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -770,9 +770,6 @@ public:
//@{
/// \brief Return pointer to this RRset's RRSIG RRset
///
- /// \exception NotImplemented Always thrown. Associated RRSIG RRsets are
- /// not supported in this class.
- ///
/// \return Null pointer, as this class does not support RRSIG records.
virtual RRsetPtr getRRsig() const {
return (RRsetPtr());
diff --git a/src/lib/dns/rrset_collection.cc b/src/lib/dns/rrset_collection.cc
new file mode 100644
index 0000000..8711c3f
--- /dev/null
+++ b/src/lib/dns/rrset_collection.cc
@@ -0,0 +1,128 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+using namespace isc;
+
+namespace isc {
+namespace dns {
+
+void
+RRsetCollection::loaderCallback(const std::string&, size_t, const std::string&)
+{
+ // We just ignore callbacks for errors and warnings.
+}
+
+void
+RRsetCollection::addRRset(RRsetPtr rrset) {
+ const CollectionKey key(rrset->getClass(), rrset->getType(),
+ rrset->getName());
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ isc_throw(InvalidParameter,
+ "RRset for " << rrset->getName() << "/" << rrset->getClass()
+ << " with type " << rrset->getType() << " already exists");
+ }
+
+ rrsets_.insert(std::pair<CollectionKey, RRsetPtr>(key, rrset));
+}
+
+template<typename T>
+void
+RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass)
+{
+ RRCollator collator(boost::bind(&RRsetCollection::addRRset, this, _1));
+ MasterLoaderCallbacks callbacks
+ (boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3),
+ boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3));
+ MasterLoader loader(source, origin, rrclass, callbacks,
+ collator.getCallback(),
+ MasterLoader::DEFAULT);
+ loader.load();
+ collator.flush();
+}
+
+RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper(filename, origin, rrclass);
+}
+
+RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper<std::istream&>(input_stream, origin, rrclass);
+}
+
+RRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) {
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (RRsetPtr());
+}
+
+ConstRRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const
+{
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (ConstRRsetPtr());
+}
+
+bool
+RRsetCollection::removeRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ const CollectionKey key(rrclass, rrtype, name);
+
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it == rrsets_.end()) {
+ return (false);
+ }
+
+ rrsets_.erase(it);
+ return (true);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+ CollectionMap::iterator it = rrsets_.begin();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+ CollectionMap::iterator it = rrsets_.end();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/rrset_collection.h b/src/lib/dns/rrset_collection.h
new file mode 100644
index 0000000..62dd9a9
--- /dev/null
+++ b/src/lib/dns/rrset_collection.h
@@ -0,0 +1,172 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_H
+#define RRSET_COLLECTION_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <map>
+
+namespace isc {
+namespace dns {
+
+/// \brief libdns++ implementation of RRsetCollectionBase using an STL
+/// container.
+class RRsetCollection : public RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// This constructor creates an empty collection without any data in
+ /// it. RRsets can be added to the collection with the \c addRRset()
+ /// method.
+ RRsetCollection() {}
+
+ /// \brief Constructor.
+ ///
+ /// The \c origin and \c rrclass arguments are required for the zone
+ /// loading, but \c RRsetCollection itself does not do any
+ /// validation, and the collection of RRsets does not have to form a
+ /// valid zone.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param filename Name of a file containing a collection of RRs in
+ /// the master file format (which may or may not form a valid zone).
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(const char* filename, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Constructor.
+ ///
+ /// This constructor is similar to the previous one, but instead of
+ /// taking a filename to load a zone from, it takes an input
+ /// stream.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param input_stream The input stream to load from.
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(std::istream& input_stream, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief Add an RRset to the collection.
+ ///
+ /// Does not do any validation whether \c rrset belongs to a
+ /// particular zone or not. A reference to \c rrset is taken in an
+ /// internally managed \c shared_ptr, so even if the caller's
+ /// \c RRsetPtr is destroyed, the RRset it wrapped is still alive
+ /// and managed by the \c RRsetCollection. It throws an
+ /// \c isc::InvalidParameter exception if an rrset with the same
+ /// class, type and name already exists.
+ ///
+ /// Callers must not modify the RRset after adding it to the
+ /// collection, as the rrset is indexed internally by the
+ /// collection.
+ void addRRset(isc::dns::RRsetPtr rrset);
+
+ /// \brief Remove an RRset from the collection.
+ ///
+ /// RRset(s) matching the \c name, \c rrclass and \c rrtype are
+ /// removed from the collection.
+ ///
+ /// \return \c true if a matching RRset was deleted, \c false if no
+ /// such RRset exists.
+ bool removeRRset(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+ /// \brief Find a matching RRset in the collection (non-const
+ /// variant).
+ ///
+ /// See above for a description of the method and arguments.
+ isc::dns::RRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+private:
+ template<typename T>
+ void constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+ void loaderCallback(const std::string&, size_t, const std::string&);
+
+ typedef boost::tuple<isc::dns::RRClass, isc::dns::RRType, isc::dns::Name>
+ CollectionKey;
+ typedef std::map<CollectionKey, isc::dns::RRsetPtr> CollectionMap;
+
+ CollectionMap rrsets_;
+
+protected:
+ class DnsIter : public RRsetCollectionBase::Iter {
+ public:
+ DnsIter(CollectionMap::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ isc::dns::RRsetPtr& rrset = iter_->second;
+ return (*rrset);
+ }
+
+ virtual IterPtr getNext() {
+ CollectionMap::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const DnsIter* other_real = dynamic_cast<DnsIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ CollectionMap::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning();
+ virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
new file mode 100644
index 0000000..5ae172a
--- /dev/null
+++ b/src/lib/dns/rrset_collection_base.h
@@ -0,0 +1,195 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_BASE_H
+#define RRSET_COLLECTION_BASE_H 1
+
+#include <dns/rrset.h>
+#include <dns/name.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+
+namespace isc {
+namespace dns {
+
+/// \brief Error during RRsetCollectionBase find() operation
+///
+/// This exception is thrown when an calling implementation of
+/// \c RRsetCollectionBase::find() results in an error which is not due
+/// to unmatched data, but because of some other underlying error
+/// condition.
+class RRsetCollectionError : public Exception {
+public:
+ RRsetCollectionError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Generic class to represent a set of RRsets.
+///
+/// This is a generic container and the stored set of RRsets does not
+/// necessarily form a valid zone (e.g. there doesn't necessarily have
+/// to be an SOA at the "origin"). Instead, it will be used to represent
+/// a single zone for the purpose of zone loading/checking. It provides
+/// a simple find() method to find an RRset for the given name and type
+/// (and maybe class) and a way to iterate over all RRsets.
+///
+/// See \c RRsetCollection for a simple libdns++ implementation using an
+/// STL container. libdatasrc will have another implementation.
+class RRsetCollectionBase {
+public:
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// This method's implementations currently are not specified to
+ /// handle \c RRTypes such as RRSIG and NSEC3. RRSIGs are attached
+ /// to their corresponding \c RRset and it is not straightforward to
+ /// search for them. Searching for RRSIGs will return \c false
+ /// always. Support for RRSIGs may be added in the future.
+ ///
+ /// Non-concrete types such as ANY and AXFR are unsupported and will
+ /// return \c false always.
+ ///
+ /// \throw RRsetCollectionError if find() results in some
+ /// implementation-specific error.
+ /// \param name The name of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find
+ (const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype)
+ const = 0;
+
+ /// \brief Destructor
+ virtual ~RRsetCollectionBase() {}
+
+protected:
+ class Iter; // forward declaration
+
+ /// \brief Wraps Iter with a reference count.
+ typedef boost::shared_ptr<Iter> IterPtr;
+
+ /// \brief A helper iterator interface for \c RRsetCollectionBase.
+ ///
+ /// This is a protected iterator class that is a helper interface
+ /// used by the public iterator. Derived classes of
+ /// \c RRsetCollectionBase are supposed to implement this class and
+ /// the \c getBeginning() and \c getEnd() methods, so that the
+ /// public interator interface can be provided. This is a forward
+ /// iterator only.
+ class Iter {
+ public:
+ virtual ~Iter() {};
+
+ /// \brief Returns the \c AbstractRRset currently pointed to by
+ /// the iterator.
+ virtual const isc::dns::AbstractRRset& getValue() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to
+ /// the next \c AbstractRRset in sequence in the collection.
+ virtual IterPtr getNext() = 0;
+
+ /// \brief Check if another iterator is equal to this one.
+ ///
+ /// Returns \c true if this iterator is equal to \c other,
+ /// \c false otherwise. Note that if \c other is not the same
+ /// type as \c this, or cannot be compared meaningfully, the
+ /// method must return \c false.
+ ///
+ /// \param other The other iterator to compare against.
+ /// \return \c true if equal, \c false otherwise.
+ virtual bool equals(Iter& other) = 0;
+ };
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
+ /// beginning of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getBeginning() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
+ /// end of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getEnd() = 0;
+
+public:
+ /// \brief A forward \c std::iterator for \c RRsetCollectionBase.
+ ///
+ /// It behaves like a \c std::iterator forward iterator, so please
+ /// see its documentation for usage.
+ class Iterator : std::iterator<std::forward_iterator_tag,
+ const isc::dns::AbstractRRset>
+ {
+ public:
+ explicit Iterator(IterPtr iter) :
+ iter_(iter)
+ {}
+
+ reference operator*() {
+ return (iter_->getValue());
+ }
+
+ Iterator& operator++() {
+ iter_ = iter_->getNext();
+ return (*this);
+ }
+
+ Iterator operator++(int) {
+ Iterator tmp(iter_);
+ ++*this;
+ return (tmp);
+ }
+
+ bool operator==(const Iterator& other) const {
+ return (iter_->equals(*other.iter_));
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return (!iter_->equals(*other.iter_));
+ }
+
+ private:
+ IterPtr iter_;
+ };
+
+ /// \brief Returns an iterator pointing to the beginning of the
+ /// collection.
+ Iterator begin() {
+ return Iterator(getBeginning());
+ }
+
+ /// \brief Returns an iterator pointing past the end of the
+ /// collection.
+ Iterator end() {
+ return Iterator(getEnd());
+ }
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_BASE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrttl.cc b/src/lib/dns/rrttl.cc
index c47ec1f..e7f8441 100644
--- a/src/lib/dns/rrttl.cc
+++ b/src/lib/dns/rrttl.cc
@@ -57,9 +57,14 @@ Unit units[] = {
namespace isc {
namespace dns {
-RRTTL::RRTTL(const std::string& ttlstr) {
+namespace {
+bool
+parseTTLString(const string& ttlstr, uint32_t& ttlval, string* error_txt) {
if (ttlstr.empty()) {
- isc_throw(InvalidRRTTL, "Empty TTL string");
+ if (error_txt != NULL) {
+ *error_txt = "Empty TTL string";
+ }
+ return (false);
}
// We use a larger data type during the computation. This is because
// some compilers don't fail when out of range, so we check the range
@@ -80,8 +85,10 @@ RRTTL::RRTTL(const std::string& ttlstr) {
if (unit == end) {
if (units_mode) {
// We had some units before. The last one is missing unit.
- isc_throw(InvalidRRTTL, "Missing the last unit: " <<
- ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "Missing the last unit: " + ttlstr;
+ }
+ return (false);
} else {
// Case without any units at all. Just convert and store
// it.
@@ -102,12 +109,18 @@ RRTTL::RRTTL(const std::string& ttlstr) {
}
}
if (!found) {
- isc_throw(InvalidRRTTL, "Unknown unit used: " << *unit <<
- " in: " << ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "Unknown unit used: " +
+ boost::lexical_cast<string>(*unit) + " in: " + ttlstr;
+ }
+ return (false);
}
// Now extract the number.
if (unit == pos) {
- isc_throw(InvalidRRTTL, "Missing number in TTL: " << ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "Missing number in TTL: " + ttlstr;
+ }
+ return (false);
}
const int64_t value = boost::lexical_cast<int64_t>(string(pos,
unit));
@@ -118,21 +131,48 @@ RRTTL::RRTTL(const std::string& ttlstr) {
// there's no need to continue).
if (value < 0 || value > 0xffffffff || val < 0 ||
val > 0xffffffff) {
- isc_throw(InvalidRRTTL, "Part of TTL out of range: " <<
- ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "Part of TTL out of range: " + ttlstr;
+ }
+ return (false);
}
// Move to after the unit.
pos = unit + 1;
}
} catch (const boost::bad_lexical_cast&) {
- isc_throw(InvalidRRTTL, "invalid TTL: " << ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "invalid TTL: " + ttlstr;
+ }
+ return (false);
}
if (val >= 0 && val <= 0xffffffff) {
- ttlval_ = val;
+ ttlval = val;
} else {
- isc_throw(InvalidRRTTL, "TTL out of range: " << ttlstr);
+ if (error_txt != NULL) {
+ *error_txt = "TTL out of range: " + ttlstr;
+ }
+ return (false);
+ }
+
+ return (true);
+}
+}
+
+RRTTL::RRTTL(const std::string& ttlstr) {
+ string error_txt;
+ if (!parseTTLString(ttlstr, ttlval_, &error_txt)) {
+ isc_throw(InvalidRRTTL, error_txt);
+ }
+}
+
+MaybeRRTTL
+RRTTL::createFromText(const string& ttlstr) {
+ uint32_t ttlval;
+ if (parseTTLString(ttlstr, ttlval, NULL)) {
+ return (MaybeRRTTL(ttlval));
}
+ return (MaybeRRTTL());
}
RRTTL::RRTTL(InputBuffer& buffer) {
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index 5acd3b1..23d57f4 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -15,10 +15,12 @@
#ifndef RRTTL_H
#define RRTTL_H 1
-#include <stdint.h>
-
#include <exceptions/exceptions.h>
+#include <boost/optional.hpp>
+
+#include <stdint.h>
+
namespace isc {
namespace util {
class InputBuffer;
@@ -30,6 +32,16 @@ namespace dns {
// forward declarations
class AbstractMessageRenderer;
+class RRTTL; // forward declaration to define MaybeRRTTL
+
+/// \brief A shortcut for a compound type to represent RRTTL-or-not.
+///
+/// A value of this type can be interpreted in a boolean context, whose
+/// value is \c true if and only if it contains a valid RRTTL object.
+/// And, if it contains a valid RRTTL object, its value is accessible
+/// using \c operator*, just like a bare pointer to \c RRTTL.
+typedef boost::optional<RRTTL> MaybeRRTTL;
+
///
/// \brief A standard DNS module exception that is thrown if an RRTTL object
/// is being constructed from an unrecognized string.
@@ -61,7 +73,7 @@ public:
class RRTTL {
public:
///
- /// \name Constructors and Destructor
+ /// \name Constructors, Factory and Destructor
///
/// Note: We use the default copy constructor and the default copy
/// assignment operator intentionally.
@@ -72,6 +84,7 @@ public:
///
/// \param ttlval An 32-bit integer of the RRTTL.
explicit RRTTL(uint32_t ttlval) : ttlval_(ttlval) {}
+
/// Constructor from a string.
///
/// It accepts either a decimal number, specifying number of seconds. Or,
@@ -87,6 +100,7 @@ public:
/// \throw InvalidRRTTL in case the string is not recognized as valid
/// TTL representation.
explicit RRTTL(const std::string& ttlstr);
+
/// Constructor from wire-format data.
///
/// The \c buffer parameter normally stores a complete DNS message
@@ -98,6 +112,39 @@ public:
///
/// \param buffer A buffer storing the wire format data.
explicit RRTTL(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRTTL from text.
+ ///
+ /// This static method is similar to the constructor that takes a string
+ /// object, but works as a factory and reports parsing failure in the
+ /// form of the return value. Normally the constructor version should
+ /// suffice, but in some cases the caller may have to expect mixture of
+ /// valid and invalid input, and may want to minimize the overhead of
+ /// possible exception handling. This version is provided for such
+ /// purpose.
+ ///
+ /// If the given text represents a valid RRTTL, it returns a \c MaybeRRTTL
+ /// object that stores a corresponding \c RRTTL object, which is
+ /// accessible via \c operator*(). In this case the returned object will
+ /// be interpreted as \c true in a boolean context. If the given text
+ /// does not represent a valid RRTTL, it returns a \c MaybeRRTTL object
+ /// which is interpreted as \c false in a boolean context.
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR TTL. For this
+ /// reason this function intentionally omits the capability of delivering
+ /// a detailed reason for the parse failure, such as in the \c want()
+ /// string when exception is thrown from the constructor (it will
+ /// internally require a creation of string object, which is relatively
+ /// expensive). If such detailed information is necessary, the constructor
+ /// version should be used to catch the resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRTTL exception.
+ ///
+ /// \param ttlstr A string representation of the \c RRTTL.
+ /// \return An MaybeRRTTL object either storing an RRTTL object for
+ /// the given text or a \c false value.
+ static MaybeRRTTL createFromText(const std::string& ttlstr);
///
//@}
@@ -236,6 +283,22 @@ public:
{ return (ttlval_ > other.ttlval_); }
//@}
+ ///
+ /// \name Protocol constants
+ ///
+ //@{
+ /// \brief The TTL of the max allowable value, per RFC2181 Section 8.
+ ///
+ /// The max value is the largest unsigned 31 bit integer, 2^31-1.
+ ///
+ /// \note At the moment an RRTTL object can have a value larger than
+ /// this limit. We may revisit it in a future version.
+ static const RRTTL& MAX_TTL() {
+ static const RRTTL max_ttl(0x7fffffff);
+ return (max_ttl);
+ }
+ //@}
+
private:
uint32_t ttlval_;
};
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index 4ef4e67..84892bf 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -31,8 +31,11 @@ using isc::dns::RRType;
namespace isc {
namespace dns {
-RRType::RRType(const std::string& typestr) {
- typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
+RRType::RRType(const std::string& type_str) {
+ if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode_)) {
+ isc_throw(InvalidRRType,
+ "Unrecognized RR type string: " + type_str);
+ }
}
RRType::RRType(InputBuffer& buffer) {
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 33867da..5029681 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -27,15 +27,18 @@ run_unittests_SOURCES += labelsequence_unittest.cc
run_unittests_SOURCES += messagerenderer_unittest.cc
run_unittests_SOURCES += master_lexer_token_unittest.cc
run_unittests_SOURCES += master_lexer_unittest.cc
+run_unittests_SOURCES += master_loader_unittest.cc
run_unittests_SOURCES += master_lexer_state_unittest.cc
run_unittests_SOURCES += name_unittest.cc
run_unittests_SOURCES += nsec3hash_unittest.cc
run_unittests_SOURCES += rrclass_unittest.cc rrtype_unittest.cc
run_unittests_SOURCES += rrttl_unittest.cc
+run_unittests_SOURCES += rrcollator_unittest.cc
run_unittests_SOURCES += opcode_unittest.cc
run_unittests_SOURCES += rcode_unittest.cc
run_unittests_SOURCES += rdata_unittest.h rdata_unittest.cc
run_unittests_SOURCES += rdatafields_unittest.cc
+run_unittests_SOURCES += rdata_char_string_unittest.cc
run_unittests_SOURCES += rdata_in_a_unittest.cc rdata_in_aaaa_unittest.cc
run_unittests_SOURCES += rdata_ns_unittest.cc rdata_soa_unittest.cc
run_unittests_SOURCES += rdata_txt_like_unittest.cc
@@ -70,7 +73,9 @@ run_unittests_SOURCES += tsig_unittest.cc
run_unittests_SOURCES += tsigerror_unittest.cc
run_unittests_SOURCES += tsigkey_unittest.cc
run_unittests_SOURCES += tsigrecord_unittest.cc
-run_unittests_SOURCES += character_string_unittest.cc
+run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
# We shouldn't need to include BOTAN_LIBS here, but there
diff --git a/src/lib/dns/tests/character_string_unittest.cc b/src/lib/dns/tests/character_string_unittest.cc
deleted file mode 100644
index 5fed9eb..0000000
--- a/src/lib/dns/tests/character_string_unittest.cc
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#include <gtest/gtest.h>
-
-#include <dns/rdata.h>
-#include <dns/tests/unittest_util.h>
-#include <dns/character_string.h>
-
-using isc::UnitTestUtil;
-
-using namespace std;
-using namespace isc;
-using namespace isc::dns;
-using namespace isc::dns::characterstr;
-using namespace isc::dns::rdata;
-
-namespace {
-
-class CharacterString {
-public:
- CharacterString(const string& str){
- string::const_iterator it = str.begin();
- characterStr_ = getNextCharacterString(str, it);
- }
- const string& str() const { return characterStr_; }
-private:
- string characterStr_;
-};
-
-TEST(CharacterStringTest, testNormalCase) {
- CharacterString cstr1("foo");
- EXPECT_EQ(string("foo"), cstr1.str());
-
- // Test <character-string> that separated by space
- CharacterString cstr2("foo bar");
- EXPECT_EQ(string("foo"), cstr2.str());
-
- // Test <character-string> that separated by quotes
- CharacterString cstr3("\"foo bar\"");
- EXPECT_EQ(string("foo bar"), cstr3.str());
-
- // Test <character-string> that not separate by quotes but ended with quotes
- CharacterString cstr4("foo\"");
- EXPECT_EQ(string("foo\""), cstr4.str());
-}
-
-TEST(CharacterStringTest, testBadCase) {
- // The <character-string> that started with quotes should also be ended
- // with quotes
- EXPECT_THROW(CharacterString cstr("\"foo"), InvalidRdataText);
-
- // The string length cannot exceed 255 characters
- string str;
- for (int i = 0; i < 257; ++i) {
- str += 'A';
- }
- EXPECT_THROW(CharacterString cstr(str), CharStringTooLong);
-}
-
-TEST(CharacterStringTest, testEscapeCharacter) {
- CharacterString cstr1("foo\\bar");
- EXPECT_EQ(string("foobar"), cstr1.str());
-
- CharacterString cstr2("foo\\\\bar");
- EXPECT_EQ(string("foo\\bar"), cstr2.str());
-
- CharacterString cstr3("fo\\111bar");
- EXPECT_EQ(string("foobar"), cstr3.str());
-
- CharacterString cstr4("fo\\1112bar");
- EXPECT_EQ(string("foo2bar"), cstr4.str());
-
- // There must be at least 3 digits followed by '\'
- EXPECT_THROW(CharacterString cstr("foo\\98ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\9ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\98"), InvalidRdataText);
-}
-
-} // namespace
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
index db0cbec..a26b02c 100644
--- a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -29,10 +30,13 @@ using namespace isc::dns::master_lexer_internal;
namespace {
+const char* const test_input =
+ "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n";
+
class InputSourceTest : public ::testing::Test {
protected:
InputSourceTest() :
- str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+ str_(test_input),
str_length_(strlen(str_)),
iss_(str_),
source_(iss_)
@@ -73,6 +77,7 @@ checkGetAndUngetChar(InputSource& source,
{
for (size_t i = 0; i < str_length; ++i) {
EXPECT_EQ(str[i], source.getChar());
+ EXPECT_EQ(i + 1, source.getPosition());
EXPECT_FALSE(source.atEOF());
}
@@ -85,6 +90,10 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be set.
EXPECT_TRUE(source.atEOF());
+ // It doesn't increase the position count.
+ EXPECT_EQ(str_length, source.getPosition());
+ EXPECT_EQ(str_length, source.getSize()); // this should be == getSize().
+
// Now, let's go backwards. This should cause the EOF to be set to
// false.
source.ungetChar();
@@ -92,6 +101,9 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be false.
EXPECT_FALSE(source.atEOF());
+ // But the position shouldn't change.
+ EXPECT_EQ(str_length, source.getPosition());
+
// This should cause EOF to be set again.
EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
@@ -106,6 +118,7 @@ checkGetAndUngetChar(InputSource& source,
// Skip one character.
source.ungetChar();
EXPECT_EQ(str[index], source.getChar());
+ EXPECT_EQ(index + 1, source.getPosition());
// Skip the character we received again.
source.ungetChar();
}
@@ -144,6 +157,7 @@ TEST_F(InputSourceTest, ungetAll) {
// Now we are back to where we started.
EXPECT_EQ(1, source_.getCurrentLine());
EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(0, source_.getPosition());
}
TEST_F(InputSourceTest, compact) {
@@ -175,6 +189,9 @@ TEST_F(InputSourceTest, compact) {
EXPECT_TRUE(source_.atEOF());
EXPECT_EQ(4, source_.getCurrentLine());
+ // compact shouldn't change the position count.
+ EXPECT_EQ(source_.getSize(), source_.getPosition());
+
// Skip the EOF.
source_.ungetChar();
@@ -322,4 +339,36 @@ TEST_F(InputSourceTest, saveLine) {
EXPECT_FALSE(source_.atEOF());
}
+TEST_F(InputSourceTest, getSize) {
+ // A simple case using string stream
+ EXPECT_EQ(strlen(test_input), source_.getSize());
+
+ // Check it works with an empty input
+ istringstream iss("");
+ EXPECT_EQ(0, InputSource(iss).getSize());
+
+ // Pretend there's an error in seeking in the stream. It will be
+ // considered a seek specific error, and getSize() returns "unknown".
+ iss.setstate(std::ios_base::failbit);
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize());
+ // The fail bit should have been cleared.
+ EXPECT_FALSE(iss.fail());
+
+ // Pretend there's a *critical* error in the stream. The constructor will
+ // throw in the attempt of getting the input size.
+ iss.setstate(std::ios_base::badbit);
+ EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError);
+
+ // Check with input source from file name. We hardcode the file size
+ // for simplicity. It won't change too often.
+ EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize());
+}
+
+TEST_F(InputSourceTest, getPosition) {
+ // Initially the position is set to 0. Other cases are tested in tests
+ // for get and unget.
+ EXPECT_EQ(0, source_.getPosition());
+ EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition());
+}
+
} // end namespace
diff --git a/src/lib/dns/tests/master_lexer_state_unittest.cc b/src/lib/dns/tests/master_lexer_state_unittest.cc
index bcee7fd..2be43db 100644
--- a/src/lib/dns/tests/master_lexer_state_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_state_unittest.cc
@@ -24,7 +24,7 @@ using namespace isc::dns;
using namespace master_lexer_internal;
namespace {
-typedef MasterLexer::Token Token; // shortcut
+typedef MasterToken Token; // shortcut
class MasterLexerStateTest : public ::testing::Test {
protected:
@@ -32,6 +32,8 @@ protected:
s_null(NULL),
s_crlf(State::getInstance(State::CRLF)),
s_string(State::getInstance(State::String)),
+ s_qstring(State::getInstance(State::QString)),
+ s_number(State::getInstance(State::Number)),
options(MasterLexer::NONE),
orig_options(options)
{}
@@ -42,6 +44,8 @@ protected:
const State* const s_null;
const State& s_crlf;
const State& s_string;
+ const State& s_qstring;
+ const State& s_number;
std::stringstream ss;
MasterLexer::Options options, orig_options;
};
@@ -111,8 +115,7 @@ TEST_F(MasterLexerStateTest, parentheses) {
EXPECT_EQ(1, s_crlf.getParenCount(lexer)); // check post condition
EXPECT_FALSE(s_crlf.wasLastEOL(lexer));
- // skip 'a' (note: until #2373 it's actually skipped as part of the '('
- // handling)
+ // skip 'a'
s_string.handle(lexer);
// Then handle ')'. '\n' before ')' isn't recognized because
@@ -187,13 +190,16 @@ TEST_F(MasterLexerStateTest, unbalancedParentheses) {
}
TEST_F(MasterLexerStateTest, startToComment) {
- // Begin with 'start', skip space, then encounter a comment. Skip
+ // Begin with 'start', detect space, then encounter a comment. Skip
// the rest of the line, and recognize the new line. Note that the
// second ';' is simply ignored.
ss << " ;a;\n";
ss << ";a;"; // Likewise, but the comment ends with EOF.
lexer.pushSource(ss);
+ // Initial whitespace (asked for in common_options)
+ EXPECT_EQ(s_null, State::start(lexer, common_options));
+ EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
// Comment ending with EOL
EXPECT_EQ(s_null, State::start(lexer, common_options));
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
@@ -226,7 +232,7 @@ TEST_F(MasterLexerStateTest, crlf) {
// 1. A sequence of \r, \n is recognized as a single 'end-of-line'
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
- EXPECT_EQ(s_null, s_crlf.handle(lexer)); // recognize '\n'
+ s_crlf.handle(lexer); // recognize '\n'
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
@@ -234,23 +240,371 @@ TEST_F(MasterLexerStateTest, crlf) {
// 'end-of-line'. then there will be "initial WS"
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
// see ' ', "unget" it
- EXPECT_EQ(s_null, s_crlf.handle(lexer));
+ s_crlf.handle(lexer);
EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize ' '
EXPECT_EQ(Token::INITIAL_WS, s_crlf.getToken(lexer).getType());
// 3. comment between \r and \n
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
// skip comments, recognize '\n'
- EXPECT_EQ(s_null, s_crlf.handle(lexer));
+ s_crlf.handle(lexer);
EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // skip 'a'
// 4. \r then EOF
EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // recognize '\r'
// see EOF, then "unget" it
- EXPECT_EQ(s_null, s_crlf.handle(lexer));
+ s_crlf.handle(lexer);
EXPECT_EQ(s_null, State::start(lexer, common_options)); // recognize EOF
EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
}
+// Commonly used check for string related test cases, checking if the given
+// token has expected values.
+void
+stringTokenCheck(const std::string& expected, const MasterToken& token,
+ bool quoted = false)
+{
+ EXPECT_EQ(quoted ? Token::QSTRING : Token::STRING, token.getType());
+ EXPECT_EQ(expected, token.getString());
+ const std::string actual(token.getStringRegion().beg,
+ token.getStringRegion().beg +
+ token.getStringRegion().len);
+ EXPECT_EQ(expected, actual);
+
+ // There should be "hidden" nul-terminator after the string data.
+ ASSERT_NE(static_cast<const char*>(NULL), token.getStringRegion().beg);
+ EXPECT_EQ(0, *(token.getStringRegion().beg + token.getStringRegion().len));
+}
+
+TEST_F(MasterLexerStateTest, string) {
+ // Check with simple strings followed by separate characters
+ ss << "followed-by-EOL\n";
+ ss << "followed-by-CR\r";
+ ss << "followed-by-space ";
+ ss << "followed-by-tab\t";
+ ss << "followed-by-comment;this is comment and ignored\n";
+ ss << "followed-by-paren(closing)";
+ ss << "followed-by-EOF";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \n
+ EXPECT_FALSE(s_string.wasLastEOL(lexer));
+ stringTokenCheck("followed-by-EOL", s_string.getToken(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \r
+ stringTokenCheck("followed-by-CR", s_string.getToken(lexer));
+ EXPECT_EQ(&s_crlf, State::start(lexer, common_options)); // handle \r...
+ s_crlf.handle(lexer); // ...and skip it
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' '
+ stringTokenCheck("followed-by-space", s_string.getToken(lexer));
+
+ // skip ' ', then recognize the next string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \t
+ stringTokenCheck("followed-by-tab", s_string.getToken(lexer));
+
+ // skip \t, then recognize the next string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see comment
+ stringTokenCheck("followed-by-comment", s_string.getToken(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see '('
+ stringTokenCheck("followed-by-paren", s_string.getToken(lexer));
+ EXPECT_EQ(&s_string, State::start(lexer, common_options)); // str in ()
+ s_string.handle(lexer); // recognize the str, see ')'
+ stringTokenCheck("closing", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see EOF
+ stringTokenCheck("followed-by-EOF", s_string.getToken(lexer));
+}
+
+TEST_F(MasterLexerStateTest, stringEscape) {
+ // some of the separate characters should be considered part of the
+ // string if escaped.
+ ss << "escaped\\ space ";
+ ss << "escaped\\\ttab ";
+ ss << "escaped\\(paren ";
+ ss << "escaped\\)close ";
+ ss << "escaped\\;comment ";
+ ss << "escaped\\\\ backslash "; // second '\' shouldn't escape ' '
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\ space", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\\ttab", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\(paren", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\)close", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("escaped\\;comment", s_string.getToken(lexer));
+
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' in mid
+ stringTokenCheck("escaped\\\\", s_string.getToken(lexer));
+
+ // Confirm the word that follows the escaped '\' is correctly recognized.
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("backslash", s_string.getToken(lexer));
+}
+
+TEST_F(MasterLexerStateTest, quotedString) {
+ ss << "\"ignore-quotes\"\n";
+ ss << "\"quoted string\" "; // space is part of the qstring
+ ss << "\"\" "; // empty quoted string
+ // also check other separator characters. note that \r doesn't cause
+ // UNBALANCED_QUOTES. Not sure if it's intentional, but that's how the
+ // BIND 9 version works, so we follow it (it should be too minor to matter
+ // in practice anyway)
+ ss << "\"quoted()\t\rstring\" ";
+ ss << "\"escape\\ in quote\" ";
+ ss << "\"escaped\\\"\" ";
+ ss << "\"escaped backslash\\\\\" ";
+ ss << "\"no;comment\"";
+ lexer.pushSource(ss);
+
+ // by default, '"' doesn't have any special meaning and part of string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer); // recognize str, see \n
+ stringTokenCheck("\"ignore-quotes\"", s_string.getToken(lexer));
+ EXPECT_EQ(s_null, State::start(lexer, common_options)); // skip \n after it
+ EXPECT_TRUE(s_string.wasLastEOL(lexer));
+
+ // If QSTRING is specified in option, '"' is regarded as a beginning of
+ // a quoted string.
+ const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ EXPECT_FALSE(s_string.wasLastEOL(lexer)); // EOL is canceled due to '"'
+ s_qstring.handle(lexer);
+ stringTokenCheck("quoted string", s_string.getToken(lexer), true);
+
+ // Empty string is okay as qstring
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("", s_string.getToken(lexer), true);
+
+ // Also checks other separator characters within a qstring
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("quoted()\t\rstring", s_string.getToken(lexer), true);
+
+ // escape character mostly doesn't have any effect in the qstring
+ // processing
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escape\\ in quote", s_string.getToken(lexer), true);
+
+ // The only exception is the quotation mark itself. Note that the escape
+ // only works on the quotation mark immediately after it.
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escaped\"", s_string.getToken(lexer), true);
+
+ // quoted '\' then '"'. Unlike the previous case '"' shouldn't be
+ // escaped.
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("escaped backslash\\\\", s_string.getToken(lexer), true);
+
+ // ';' has no meaning in a quoted string (not indicating a comment)
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("no;comment", s_string.getToken(lexer), true);
+}
+
+TEST_F(MasterLexerStateTest, brokenQuotedString) {
+ ss << "\"unbalanced-quote\n";
+ ss << "\"quoted\\\n\" ";
+ ss << "\"unclosed quote and EOF";
+ lexer.pushSource(ss);
+
+ // EOL is encountered without closing the quote
+ const MasterLexer::Options options = common_options | MasterLexer::QSTRING;
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNBALANCED_QUOTES,
+ s_qstring.getToken(lexer).getErrorCode());
+ // We can resume after the error from the '\n'
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+ // \n is okay in a quoted string if escaped
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ stringTokenCheck("quoted\\\n", s_string.getToken(lexer), true);
+
+ // EOF is encountered without closing the quote
+ EXPECT_EQ(&s_qstring, State::start(lexer, options));
+ s_qstring.handle(lexer);
+ ASSERT_EQ(Token::ERROR, s_qstring.getToken(lexer).getType());
+ EXPECT_EQ(Token::UNEXPECTED_END, s_qstring.getToken(lexer).getErrorCode());
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+TEST_F(MasterLexerStateTest, basicNumbers) {
+ ss << "0 ";
+ ss << "1 ";
+ ss << "12345 ";
+ ss << "4294967295 "; // 2^32-1
+ ss << "4294967296 "; // Out of range
+ ss << "340282366920938463463374607431768211456 ";
+ // Very much out of range (2^128)
+ ss << "005 "; // Leading zeroes are ignored
+ ss << "42;asdf\n"; // Number with comment
+ ss << "37"; // Simple number again, here to make
+ // sure none of the above messed up
+ // the tokenizer
+ lexer.pushSource(ss);
+
+ // Ask the lexer to recognize numbers as well
+ const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(0, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(1, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(12345, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(4294967295u, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+ s_number.getToken(lexer).getErrorCode());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(Token::NUMBER_OUT_OF_RANGE,
+ s_number.getToken(lexer).getErrorCode());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(5, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(42, s_number.getToken(lexer).getNumber());
+
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_TRUE(s_crlf.wasLastEOL(lexer));
+ EXPECT_EQ(Token::END_OF_LINE, s_crlf.getToken(lexer).getType());
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ EXPECT_EQ(37, s_number.getToken(lexer).getNumber());
+
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
}
+
+// Test tokens that look like (or start out as) numbers,
+// but turn out to be strings. Tests include escaped characters.
+TEST_F(MasterLexerStateTest, stringNumbers) {
+ ss << "123 "; // Should be read as a string if the
+ // NUMBER option is not given
+ ss << "-1 "; // Negative numbers are interpreted
+ // as strings (unsigned integers only)
+ ss << "123abc456 "; // 'Numbers' containing non-digits should
+ // be interpreted as strings
+ ss << "123\\456 "; // Numbers containing escaped digits are
+ // interpreted as strings
+ ss << "3scaped\\ space ";
+ ss << "3scaped\\\ttab ";
+ ss << "3scaped\\(paren ";
+ ss << "3scaped\\)close ";
+ ss << "3scaped\\;comment ";
+ ss << "3scaped\\\\ 8ackslash "; // second '\' shouldn't escape ' '
+
+ lexer.pushSource(ss);
+
+ // Note that common_options does not include MasterLexer::NUMBER,
+ // so the token should be recognized as a string
+ EXPECT_EQ(&s_string, State::start(lexer, common_options));
+ s_string.handle(lexer);
+ stringTokenCheck("123", s_string.getToken(lexer), false);
+
+ // Ask the lexer to recognize numbers as well
+ const MasterLexer::Options options = common_options | MasterLexer::NUMBER;
+
+ EXPECT_EQ(&s_string, State::start(lexer, options));
+ s_string.handle(lexer);
+ stringTokenCheck("-1", s_string.getToken(lexer), false);
+
+ // Starts out as a number, but ends up being a string
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ stringTokenCheck("123abc456", s_number.getToken(lexer), false);
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer);
+ stringTokenCheck("123\\456", s_number.getToken(lexer), false);
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\ space", s_number.getToken(lexer));
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\\ttab", s_number.getToken(lexer));
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\(paren", s_number.getToken(lexer));
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\)close", s_number.getToken(lexer));
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("3scaped\\;comment", s_number.getToken(lexer));
+
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' in mid
+ stringTokenCheck("3scaped\\\\", s_number.getToken(lexer));
+
+ // Confirm the word that follows the escaped '\' is correctly recognized.
+ EXPECT_EQ(&s_number, State::start(lexer, options));
+ s_number.handle(lexer); // recognize str, see ' ' at end
+ stringTokenCheck("8ackslash", s_number.getToken(lexer));
+
+ // If we continue we'll simply see the EOF
+ EXPECT_EQ(s_null, State::start(lexer, options));
+ EXPECT_EQ(Token::END_OF_FILE, s_crlf.getToken(lexer).getType());
+}
+
+} // end anonymous namespace
+
diff --git a/src/lib/dns/tests/master_lexer_token_unittest.cc b/src/lib/dns/tests/master_lexer_token_unittest.cc
index a63b9ca..89a4f9c 100644
--- a/src/lib/dns/tests/master_lexer_token_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_token_unittest.cc
@@ -31,24 +31,27 @@ const size_t TEST_STRING_LEN = sizeof(TEST_STRING) - 1;
class MasterLexerTokenTest : public ::testing::Test {
protected:
MasterLexerTokenTest() :
- token_eof(MasterLexer::Token::END_OF_FILE),
+ token_eof(MasterToken::END_OF_FILE),
token_str(TEST_STRING, TEST_STRING_LEN),
token_num(42),
- token_err(MasterLexer::Token::UNEXPECTED_END)
+ token_err(MasterToken::UNEXPECTED_END)
{}
- const MasterLexer::Token token_eof; // an example of non-value type token
- const MasterLexer::Token token_str;
- const MasterLexer::Token token_num;
- const MasterLexer::Token token_err;
+ const MasterToken token_eof; // an example of non-value type token
+ const MasterToken token_str;
+ const MasterToken token_num;
+ const MasterToken token_err;
};
TEST_F(MasterLexerTokenTest, strings) {
// basic construction and getter checks
- EXPECT_EQ(MasterLexer::Token::STRING, token_str.getType());
+ EXPECT_EQ(MasterToken::STRING, token_str.getType());
EXPECT_EQ(std::string("string token"), token_str.getString());
- const MasterLexer::Token::StringRegion str_region =
+ std::string strval = "dummy"; // this should be replaced
+ token_str.getString(strval);
+ EXPECT_EQ(std::string("string token"), strval);
+ const MasterToken::StringRegion str_region =
token_str.getStringRegion();
EXPECT_EQ(TEST_STRING, str_region.beg);
EXPECT_EQ(TEST_STRING_LEN, str_region.len);
@@ -59,43 +62,47 @@ TEST_F(MasterLexerTokenTest, strings) {
std::string expected_str("string token");
expected_str.push_back('\0');
EXPECT_EQ(expected_str,
- MasterLexer::Token(TEST_STRING, TEST_STRING_LEN + 1).getString());
+ MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString());
+ MasterToken(TEST_STRING, TEST_STRING_LEN + 1).getString(strval);
+ EXPECT_EQ(expected_str, strval);
// Construct type of qstring
- EXPECT_EQ(MasterLexer::Token::QSTRING,
- MasterLexer::Token(TEST_STRING, sizeof(TEST_STRING), true).
+ EXPECT_EQ(MasterToken::QSTRING,
+ MasterToken(TEST_STRING, sizeof(TEST_STRING), true).
getType());
// if we explicitly set 'quoted' to false, it should be normal string
- EXPECT_EQ(MasterLexer::Token::STRING,
- MasterLexer::Token(TEST_STRING, sizeof(TEST_STRING), false).
+ EXPECT_EQ(MasterToken::STRING,
+ MasterToken(TEST_STRING, sizeof(TEST_STRING), false).
getType());
// getString/StringRegion() aren't allowed for non string(-variant) types
EXPECT_THROW(token_eof.getString(), isc::InvalidOperation);
+ EXPECT_THROW(token_eof.getString(strval), isc::InvalidOperation);
EXPECT_THROW(token_num.getString(), isc::InvalidOperation);
+ EXPECT_THROW(token_num.getString(strval), isc::InvalidOperation);
EXPECT_THROW(token_eof.getStringRegion(), isc::InvalidOperation);
EXPECT_THROW(token_num.getStringRegion(), isc::InvalidOperation);
}
TEST_F(MasterLexerTokenTest, numbers) {
EXPECT_EQ(42, token_num.getNumber());
- EXPECT_EQ(MasterLexer::Token::NUMBER, token_num.getType());
+ EXPECT_EQ(MasterToken::NUMBER, token_num.getType());
// It's copyable and assignable.
- MasterLexer::Token token(token_num);
+ MasterToken token(token_num);
EXPECT_EQ(42, token.getNumber());
- EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+ EXPECT_EQ(MasterToken::NUMBER, token.getType());
token = token_num;
EXPECT_EQ(42, token.getNumber());
- EXPECT_EQ(MasterLexer::Token::NUMBER, token.getType());
+ EXPECT_EQ(MasterToken::NUMBER, token.getType());
// it's okay to replace it with a different type of token
token = token_eof;
- EXPECT_EQ(MasterLexer::Token::END_OF_FILE, token.getType());
+ EXPECT_EQ(MasterToken::END_OF_FILE, token.getType());
// Possible max value
- token = MasterLexer::Token(0xffffffff);
+ token = MasterToken(0xffffffff);
EXPECT_EQ(4294967295u, token.getNumber());
// getNumber() isn't allowed for non number types
@@ -105,52 +112,52 @@ TEST_F(MasterLexerTokenTest, numbers) {
TEST_F(MasterLexerTokenTest, novalues) {
// Just checking we can construct them and getType() returns correct value.
- EXPECT_EQ(MasterLexer::Token::END_OF_FILE, token_eof.getType());
- EXPECT_EQ(MasterLexer::Token::END_OF_LINE,
- MasterLexer::Token(MasterLexer::Token::END_OF_LINE).getType());
- EXPECT_EQ(MasterLexer::Token::INITIAL_WS,
- MasterLexer::Token(MasterLexer::Token::INITIAL_WS).getType());
+ EXPECT_EQ(MasterToken::END_OF_FILE, token_eof.getType());
+ EXPECT_EQ(MasterToken::END_OF_LINE,
+ MasterToken(MasterToken::END_OF_LINE).getType());
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ MasterToken(MasterToken::INITIAL_WS).getType());
// Special types of tokens cannot have value-based types
- EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::STRING),
- isc::InvalidParameter);
- EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::QSTRING),
- isc::InvalidParameter);
- EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::NUMBER),
- isc::InvalidParameter);
- EXPECT_THROW(MasterLexer::Token t(MasterLexer::Token::ERROR),
- isc::InvalidParameter);
+ EXPECT_THROW(MasterToken t(MasterToken::STRING), isc::InvalidParameter);
+ EXPECT_THROW(MasterToken t(MasterToken::QSTRING), isc::InvalidParameter);
+ EXPECT_THROW(MasterToken t(MasterToken::NUMBER), isc::InvalidParameter);
+ EXPECT_THROW(MasterToken t(MasterToken::ERROR), isc::InvalidParameter);
}
TEST_F(MasterLexerTokenTest, errors) {
- EXPECT_EQ(MasterLexer::Token::ERROR, token_err.getType());
- EXPECT_EQ(MasterLexer::Token::UNEXPECTED_END, token_err.getErrorCode());
+ EXPECT_EQ(MasterToken::ERROR, token_err.getType());
+ EXPECT_EQ(MasterToken::UNEXPECTED_END, token_err.getErrorCode());
EXPECT_EQ("unexpected end of input", token_err.getErrorText());
- EXPECT_EQ("lexer not started",
- MasterLexer::Token(MasterLexer::Token::NOT_STARTED).
+ EXPECT_EQ("lexer not started", MasterToken(MasterToken::NOT_STARTED).
getErrorText());
EXPECT_EQ("unbalanced parentheses",
- MasterLexer::Token(MasterLexer::Token::UNBALANCED_PAREN).
+ MasterToken(MasterToken::UNBALANCED_PAREN).
+ getErrorText());
+ EXPECT_EQ("unbalanced quotes", MasterToken(MasterToken::UNBALANCED_QUOTES).
+ getErrorText());
+ EXPECT_EQ("no token produced", MasterToken(MasterToken::NO_TOKEN_PRODUCED).
getErrorText());
- EXPECT_EQ("unbalanced quotes",
- MasterLexer::Token(MasterLexer::Token::UNBALANCED_QUOTES).
+ EXPECT_EQ("number out of range",
+ MasterToken(MasterToken::NUMBER_OUT_OF_RANGE).
getErrorText());
+ EXPECT_EQ("not a valid number",
+ MasterToken(MasterToken::BAD_NUMBER).getErrorText());
// getErrorCode/Text() isn't allowed for non number types
EXPECT_THROW(token_num.getErrorCode(), isc::InvalidOperation);
EXPECT_THROW(token_num.getErrorText(), isc::InvalidOperation);
- // Only the pre-defined error code is accepted. Hardcoding '4' (max code
+ // Only the pre-defined error code is accepted. Hardcoding '7' (max code
// + 1) is intentional; it'd be actually better if we notice it when we
// update the enum list (which shouldn't happen too often).
- EXPECT_THROW(MasterLexer::Token(MasterLexer::Token::ErrorCode(4)),
+ EXPECT_THROW(MasterToken(MasterToken::ErrorCode(7)),
isc::InvalidParameter);
// Check the coexistence of "from number" and "from error-code"
// constructors won't cause confusion.
- EXPECT_EQ(MasterLexer::Token::NUMBER,
- MasterLexer::Token(static_cast<uint32_t>(
- MasterLexer::Token::NOT_STARTED)).
+ EXPECT_EQ(MasterToken::NUMBER,
+ MasterToken(static_cast<uint32_t>(MasterToken::NOT_STARTED)).
getType());
}
}
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 93fead7..039a0c3 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -15,10 +15,14 @@
#include <exceptions/exceptions.h>
#include <dns/master_lexer.h>
+#include <dns/master_lexer_state.h>
#include <gtest/gtest.h>
#include <boost/lexical_cast.hpp>
+#include <boost/function.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
#include <string>
#include <sstream>
@@ -27,6 +31,8 @@ using namespace isc::dns;
using std::string;
using std::stringstream;
using boost::lexical_cast;
+using boost::scoped_ptr;
+using master_lexer_internal::State;
namespace {
@@ -46,6 +52,7 @@ void
checkEmptySource(const MasterLexer& lexer) {
EXPECT_TRUE(lexer.getSourceName().empty());
EXPECT_EQ(0, lexer.getSourceLine());
+ EXPECT_EQ(0, lexer.getPosition());
}
TEST_F(MasterLexerTest, preOpen) {
@@ -54,8 +61,12 @@ TEST_F(MasterLexerTest, preOpen) {
}
TEST_F(MasterLexerTest, pushStream) {
+ EXPECT_EQ(0, lexer.getSourceCount());
+ ss << "test";
lexer.pushSource(ss);
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ EXPECT_EQ(1, lexer.getSourceCount());
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test")
// From the point of view of this test, we only have to check (though
// indirectly) getSourceLine calls InputSource::getCurrentLine. It should
@@ -64,18 +75,37 @@ TEST_F(MasterLexerTest, pushStream) {
// By popping it the stack will be empty again.
lexer.popSource();
+ EXPECT_EQ(0, lexer.getSourceCount());
checkEmptySource(lexer);
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change
+}
+
+TEST_F(MasterLexerTest, pushStreamFail) {
+ // Pretend a "bad" thing happened in the stream. This will make the
+ // initialization throw an exception.
+ ss << "test";
+ ss.setstate(std::ios_base::badbit);
+
+ EXPECT_THROW(lexer.pushSource(ss), isc::Unexpected);
}
TEST_F(MasterLexerTest, pushFile) {
// We use zone file (-like) data, but in this test that actually doesn't
// matter.
+ EXPECT_EQ(0, lexer.getSourceCount());
EXPECT_TRUE(lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt"));
+ EXPECT_EQ(1, lexer.getSourceCount());
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
EXPECT_EQ(1, lexer.getSourceLine());
+ // 143 = size of the test zone file. hardcode it assuming it won't change
+ // too often.
+ EXPECT_EQ(143, lexer.getTotalSourceSize());
+
lexer.popSource();
checkEmptySource(lexer);
+ EXPECT_EQ(0, lexer.getSourceCount());
+ EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change
// If we give a non NULL string pointer, its content will be intact
// if pushSource succeeds.
@@ -104,19 +134,62 @@ TEST_F(MasterLexerTest, pushFileFail) {
}
TEST_F(MasterLexerTest, nestedPush) {
+ const string test_txt = "test";
+ ss << test_txt;
lexer.pushSource(ss);
+
+ EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(0, lexer.getPosition());
+
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ // Read the string; getPosition() should reflect that.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size(), lexer.getPosition());
+
// We can push another source without popping the previous one.
lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(143 + test_txt.size(),
+ lexer.getTotalSourceSize()); // see above for magic nums
+
+ // the next token should be the EOL (skipping a comment line), its
+ // position in the file is 35 (hardcoded).
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
// popSource() works on the "topmost" (last-pushed) source
lexer.popSource();
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ // pop shouldn't change the total size and the current position
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+
lexer.popSource();
EXPECT_TRUE(lexer.getSourceName().empty());
+
+ // size and position still shouldn't change
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+}
+
+TEST_F(MasterLexerTest, unknownSourceSize) {
+ // Similar to the previous case, but the size of the second source
+ // will be considered "unknown" (by emulating an error).
+ ss << "test";
+ lexer.pushSource(ss);
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+ stringstream ss2;
+ ss2.setstate(std::ios_base::failbit); // this will make the size unknown
+ lexer.pushSource(ss2);
+ // Then the total size is also unknown.
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+
+ // Even if we pop that source, the size is still unknown.
+ lexer.popSource();
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
}
TEST_F(MasterLexerTest, invalidPop) {
@@ -124,4 +197,333 @@ TEST_F(MasterLexerTest, invalidPop) {
EXPECT_THROW(lexer.popSource(), isc::InvalidOperation);
}
+// Test it is not possible to get token when no source is available.
+TEST_F(MasterLexerTest, noSource) {
+ EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
+}
+
+// Test getting some tokens. It also check basic behavior of getPosition().
+TEST_F(MasterLexerTest, getNextToken) {
+ ss << "\n \n\"STRING\"\n";
+ lexer.pushSource(ss);
+
+ // First, the newline should get out.
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Then the whitespace, if we specify the option.
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition());
+ // The newline
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n
+ // The (quoted) string
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes
+
+ // And the end of line and file
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change
+}
+
+// Test we correctly find end of file.
+TEST_F(MasterLexerTest, eof) {
+ // Let the ss empty.
+ lexer.pushSource(ss);
+
+ // The first one is found to be EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ // And it stays on EOF for any following attempts
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ // And we can step back one token, but that is the EOF too.
+ lexer.ungetToken();
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened parentheses and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedParen) {
+ ss << "(\"string\"";
+ lexer.pushSource(ss);
+
+ // The string gets out first
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ // Then an unbalanced parenthesis
+ EXPECT_EQ(MasterToken::UNBALANCED_PAREN,
+ lexer.getNextToken().getErrorCode());
+ // And then EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Check we properly return error when there's an opened quoted string and no
+// closing one
+TEST_F(MasterLexerTest, getUnbalancedString) {
+ ss << "\"string";
+ lexer.pushSource(ss);
+
+ // Then an unbalanced qstring (reported as an unexpected end)
+ EXPECT_EQ(MasterToken::UNEXPECTED_END,
+ lexer.getNextToken(MasterLexer::QSTRING).getErrorCode());
+ // And then EOF
+ EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+}
+
+// Test ungetting tokens works. Also check getPosition() is adjusted
+TEST_F(MasterLexerTest, ungetToken) {
+ ss << "\n (\"string\"\n) more";
+ lexer.pushSource(ss);
+
+ // Try getting the newline
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Return it and get again
+ lexer.ungetToken();
+ EXPECT_EQ(0, lexer.getPosition());
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ // Get the string and return it back
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition());
+ lexer.ungetToken();
+ EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n
+ // But if we change the options, it honors them
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::QSTRING |
+ MasterLexer::INITIAL_WS).getType());
+ // Get to the "more" string
+ EXPECT_EQ(MasterToken::QSTRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(MasterToken::STRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+ // Return it back. It should get inside the parentheses.
+ // Upon next attempt to get it again, the newline inside the parentheses
+ // should be still ignored.
+ lexer.ungetToken();
+ EXPECT_EQ(MasterToken::STRING,
+ lexer.getNextToken(MasterLexer::QSTRING).getType());
+}
+
+// Check ungetting token without overriding the start method. We also
+// check it works well with changing options between the calls.
+TEST_F(MasterLexerTest, ungetRealOptions) {
+ ss << " \n";
+ lexer.pushSource(ss);
+
+ // If we call it the usual way, it skips up to the newline and returns
+ // it
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+
+ // Now we return it. If we call it again, but with different options,
+ // we get the initial whitespace.
+ lexer.ungetToken();
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+}
+
+// Check the initial whitespace is found even in the first line of included
+// file. It also confirms getPosition() works for multiple sources, each
+// of which is partially parsed.
+TEST_F(MasterLexerTest, includeAndInitialWS) {
+ ss << " \n";
+ lexer.pushSource(ss);
+
+ stringstream ss2;
+ ss2 << " \n";
+
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(1, lexer.getPosition());
+ lexer.pushSource(ss2);
+ EXPECT_EQ(MasterToken::INITIAL_WS,
+ lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions.
+}
+
+// Test only one token can be ungotten
+TEST_F(MasterLexerTest, ungetTwice) {
+ ss << "\n";
+ lexer.pushSource(ss);
+
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ // Unget the token. It can be done once
+ lexer.ungetToken();
+ // But not twice
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token before we get one
+TEST_F(MasterLexerTest, ungetBeforeGet) {
+ lexer.pushSource(ss); // Just to eliminate the missing source problem
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Test we can't unget a token after a source switch, even when we got
+// something before.
+TEST_F(MasterLexerTest, ungetAfterSwitch) {
+ ss << "\n\n";
+ lexer.pushSource(ss);
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ // Switch the source
+ std::stringstream ss2;
+ ss2 << "\n\n";
+ lexer.pushSource(ss2);
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+ // We can get from the new source
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ // And when we drop the current source, we can't unget again
+ lexer.popSource();
+ EXPECT_THROW(lexer.ungetToken(), isc::InvalidOperation);
+}
+
+// Common checks for the case when getNextToken() should result in LexerError
+void
+lexerErrorCheck(MasterLexer& lexer, MasterToken::Type expect,
+ MasterToken::ErrorCode expected_error)
+{
+ bool thrown = false;
+ try {
+ lexer.getNextToken(expect);
+ } catch (const MasterLexer::LexerError& error) {
+ EXPECT_EQ(expected_error, error.token_.getErrorCode());
+ thrown = true;
+ }
+ EXPECT_TRUE(thrown);
+}
+
+// Common checks regarding expected/unexpected end-of-line
+//
+// The 'lexer' should be at a position before two consecutive '\n's.
+// The first one will be recognized, and the second one will be considered an
+// unexpected token. Then this helper consumes the second '\n', so the caller
+// can continue the test after these '\n's.
+void
+eolCheck(MasterLexer& lexer, MasterToken::Type expect) {
+ // If EOL is found and eol_ok is true, we get it.
+ EXPECT_EQ(MasterToken::END_OF_LINE,
+ lexer.getNextToken(expect, true).getType());
+ // We'll see the second '\n'; by default it will fail.
+ EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError);
+ // Same if eol_ok is explicitly set to false. This also checks the
+ // offending '\n' was "ungotten".
+ EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError);
+
+ // And also check the error token set in the exception object.
+ lexerErrorCheck(lexer, expect, MasterToken::UNEXPECTED_END);
+
+ // Then skip the 2nd '\n'
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+}
+
+// Common checks regarding expected/unexpected end-of-file
+//
+// The 'lexer' should be at a position just before an end-of-file.
+void
+eofCheck(MasterLexer& lexer, MasterToken::Type expect) {
+ EXPECT_EQ(MasterToken::END_OF_FILE,
+ lexer.getNextToken(expect, true).getType());
+ EXPECT_THROW(lexer.getNextToken(expect), MasterLexer::LexerError);
+ EXPECT_THROW(lexer.getNextToken(expect, false), MasterLexer::LexerError);
+}
+
+TEST_F(MasterLexerTest, getNextTokenString) {
+ ss << "normal-string\n";
+ ss << "\n";
+ ss << "another-string";
+ lexer.pushSource(ss);
+
+ // Normal successful case: Expecting a string and get one.
+ EXPECT_EQ("normal-string",
+ lexer.getNextToken(MasterToken::STRING).getString());
+ eolCheck(lexer, MasterToken::STRING);
+
+ // Same set of tests but for end-of-file
+ EXPECT_EQ("another-string",
+ lexer.getNextToken(MasterToken::STRING, true).getString());
+ eofCheck(lexer, MasterToken::STRING);
+}
+
+TEST_F(MasterLexerTest, getNextTokenQString) {
+ ss << "\"quoted-string\"\n";
+ ss << "\n";
+ ss << "normal-string";
+ lexer.pushSource(ss);
+
+ // Expecting a quoted string and get one.
+ EXPECT_EQ("quoted-string",
+ lexer.getNextToken(MasterToken::QSTRING).getString());
+ eolCheck(lexer, MasterToken::QSTRING);
+
+ // Expecting a quoted string but see a normal string. It's okay.
+ EXPECT_EQ("normal-string",
+ lexer.getNextToken(MasterToken::QSTRING).getString());
+ eofCheck(lexer, MasterToken::QSTRING);
+}
+
+TEST_F(MasterLexerTest, getNextTokenNumber) {
+ ss << "3600\n";
+ ss << "\n";
+ ss << "4294967296 "; // =2^32, out of range
+ ss << "not-a-number ";
+ ss << "123abc "; // starting with digits, but resulting in a string
+ ss << "86400";
+ lexer.pushSource(ss);
+
+ // Expecting a number string and get one.
+ EXPECT_EQ(3600,
+ lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ eolCheck(lexer, MasterToken::NUMBER);
+
+ // Expecting a number, but it's too big for uint32.
+ lexerErrorCheck(lexer, MasterToken::NUMBER,
+ MasterToken::NUMBER_OUT_OF_RANGE);
+ // The token should have been "ungotten". Re-read and skip it.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+
+ // Expecting a number, but see a string.
+ lexerErrorCheck(lexer, MasterToken::NUMBER, MasterToken::BAD_NUMBER);
+ // The unexpected string should have been "ungotten". Re-read and skip it.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+
+ // Expecting a number, but see a string.
+ lexerErrorCheck(lexer, MasterToken::NUMBER, MasterToken::BAD_NUMBER);
+ // The unexpected string should have been "ungotten". Re-read and skip it.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+
+ // Unless we specify NUMBER, decimal number string should be recognized
+ // as a string.
+ EXPECT_EQ("86400",
+ lexer.getNextToken(MasterToken::STRING).getString());
+ eofCheck(lexer, MasterToken::NUMBER);
+}
+
+TEST_F(MasterLexerTest, getNextTokenErrors) {
+ // Check miscellaneous error cases
+
+ ss << ") "; // unbalanced parenthesis
+ ss << "string-after-error ";
+ lexer.pushSource(ss);
+
+ // Only string/qstring/number can be "expected".
+ EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_LINE),
+ isc::InvalidParameter);
+ EXPECT_THROW(lexer.getNextToken(MasterToken::END_OF_FILE),
+ isc::InvalidParameter);
+ EXPECT_THROW(lexer.getNextToken(MasterToken::INITIAL_WS),
+ isc::InvalidParameter);
+ EXPECT_THROW(lexer.getNextToken(MasterToken::ERROR),
+ isc::InvalidParameter);
+
+ // If it encounters a syntax error, it results in LexerError exception.
+ lexerErrorCheck(lexer, MasterToken::STRING, MasterToken::UNBALANCED_PAREN);
+
+ // Unlike the NUMBER_OUT_OF_RANGE case, the error part has been skipped
+ // within getNextToken(). We should be able to get the next token.
+ EXPECT_EQ("string-after-error",
+ lexer.getNextToken(MasterToken::STRING).getString());
+}
+
}
diff --git a/src/lib/dns/tests/master_loader_callbacks_test.cc b/src/lib/dns/tests/master_loader_callbacks_test.cc
new file mode 100644
index 0000000..aafbe24
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_callbacks_test.cc
@@ -0,0 +1,84 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrset.h>
+#include <dns/name.h>
+#include <dns/rrttl.h>
+#include <dns/rrclass.h>
+
+#include <exceptions/exceptions.h>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+namespace {
+
+using std::string;
+using namespace isc::dns;
+
+class MasterLoaderCallbacksTest : public ::testing::Test {
+protected:
+ MasterLoaderCallbacksTest() :
+ last_was_error_(false), // Not needed, but then cppcheck complains
+ issue_called_(false),
+ rrset_(new RRset(Name("example.org"), RRClass::IN(), RRType::A(),
+ RRTTL(3600))),
+ error_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+ true, _1, _2, _3)),
+ warning_(boost::bind(&MasterLoaderCallbacksTest::checkCallback, this,
+ false, _1, _2, _3)),
+ callbacks_(error_, warning_)
+ {}
+
+ void checkCallback(bool error, const string& source, size_t line,
+ const string& reason)
+ {
+ issue_called_ = true;
+ last_was_error_ = error;
+ EXPECT_EQ("source", source);
+ EXPECT_EQ(1, line);
+ EXPECT_EQ("reason", reason);
+ }
+ bool last_was_error_;
+ bool issue_called_;
+ const RRsetPtr rrset_;
+ const MasterLoaderCallbacks::IssueCallback error_, warning_;
+ MasterLoaderCallbacks callbacks_;
+};
+
+// Check the constructor rejects empty callbacks, but accepts non-empty ones
+TEST_F(MasterLoaderCallbacksTest, constructor) {
+ EXPECT_THROW(MasterLoaderCallbacks(MasterLoaderCallbacks::IssueCallback(),
+ warning_), isc::InvalidParameter);
+ EXPECT_THROW(MasterLoaderCallbacks(error_,
+ MasterLoaderCallbacks::IssueCallback()),
+ isc::InvalidParameter);
+ EXPECT_NO_THROW(MasterLoaderCallbacks(error_, warning_));
+}
+
+// Call the issue callbacks
+TEST_F(MasterLoaderCallbacksTest, issueCall) {
+ callbacks_.error("source", 1, "reason");
+ EXPECT_TRUE(last_was_error_);
+ EXPECT_TRUE(issue_called_);
+
+ issue_called_ = false;
+
+ callbacks_.warning("source", 1, "reason");
+ EXPECT_FALSE(last_was_error_);
+ EXPECT_TRUE(issue_called_);
+}
+
+}
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
new file mode 100644
index 0000000..636f73d
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -0,0 +1,955 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/rrttl.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <string>
+#include <vector>
+#include <list>
+#include <sstream>
+
+using namespace isc::dns;
+using std::vector;
+using std::string;
+using std::list;
+using std::stringstream;
+using std::endl;
+using boost::lexical_cast;
+
+namespace {
+class MasterLoaderTest : public ::testing::Test {
+public:
+ MasterLoaderTest() :
+ callbacks_(boost::bind(&MasterLoaderTest::callback, this,
+ &errors_, _1, _2, _3),
+ boost::bind(&MasterLoaderTest::callback, this,
+ &warnings_, _1, _2, _3))
+ {}
+
+ void TearDown() {
+ // Check there are no more RRs we didn't expect
+ EXPECT_TRUE(rrsets_.empty());
+ }
+
+ /// Concatenate file, line, and reason, and add it to either errors
+ /// or warnings
+ void callback(vector<string>* target, const std::string& file, size_t line,
+ const std::string& reason)
+ {
+ std::stringstream ss;
+ ss << reason << " [" << file << ":" << line << "]";
+ target->push_back(ss.str());
+ }
+
+ void addRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype, const RRTTL& rrttl,
+ const rdata::RdataPtr& data) {
+ const RRsetPtr rrset(new BasicRRset(name, rrclass, rrtype, rrttl));
+ rrset->addRdata(data);
+ rrsets_.push_back(rrset);
+ }
+
+ void setLoader(const char* file, const Name& origin,
+ const RRClass& rrclass, const MasterLoader::Options options)
+ {
+ loader_.reset(new MasterLoader(file, origin, rrclass, callbacks_,
+ boost::bind(&MasterLoaderTest::addRRset,
+ this, _1, _2, _3, _4, _5),
+ options));
+ }
+
+ void setLoader(std::istream& stream, const Name& origin,
+ const RRClass& rrclass, const MasterLoader::Options options)
+ {
+ loader_.reset(new MasterLoader(stream, origin, rrclass, callbacks_,
+ boost::bind(&MasterLoaderTest::addRRset,
+ this, _1, _2, _3, _4, _5),
+ options));
+ }
+
+ static string prepareZone(const string& line, bool include_last) {
+ string result;
+ result += "example.org. 3600 IN SOA ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200\n";
+ result += line;
+ if (include_last) {
+ result += "\n";
+ result += "correct 3600 IN A 192.0.2.2\n";
+ }
+ return (result);
+ }
+
+ void clear() {
+ warnings_.clear();
+ errors_.clear();
+ rrsets_.clear();
+ }
+
+ // Check the next RR in the ones produced by the loader
+ // Other than passed arguments are checked to be the default for the tests
+ void checkRR(const string& name, const RRType& type, const string& data,
+ const RRTTL& rrttl = RRTTL(3600)) {
+ ASSERT_FALSE(rrsets_.empty());
+ RRsetPtr current = rrsets_.front();
+ rrsets_.pop_front();
+
+ EXPECT_EQ(Name(name), current->getName());
+ EXPECT_EQ(type, current->getType());
+ EXPECT_EQ(RRClass::IN(), current->getClass());
+ EXPECT_EQ(rrttl, current->getTTL());
+ ASSERT_EQ(1, current->getRdataCount());
+ EXPECT_EQ(0, isc::dns::rdata::createRdata(type, RRClass::IN(), data)->
+ compare(current->getRdataIterator()->getCurrent()));
+ }
+
+ void checkBasicRRs() {
+ checkRR("example.org", RRType::SOA(),
+ "ns1.example.org. admin.example.org. "
+ "1234 3600 1800 2419200 7200");
+ checkRR("example.org", RRType::NS(), "ns1.example.org.");
+ checkRR("www.example.org", RRType::A(), "192.0.2.1");
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+ }
+
+ void checkARR(const string& name) {
+ checkRR(name, RRType::A(), "192.0.2.1");
+ }
+
+ MasterLoaderCallbacks callbacks_;
+ boost::scoped_ptr<MasterLoader> loader_;
+ vector<string> errors_;
+ vector<string> warnings_;
+ list<RRsetPtr> rrsets_;
+};
+
+// Test simple loading. The zone file contains no tricky things, and nothing is
+// omitted. No RRset contains more than one RR Also no errors or warnings.
+TEST_F(MasterLoaderTest, basicLoad) {
+ setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // The following three should be set to 0 initially in case the loader
+ // is constructed from a file name.
+ EXPECT_EQ(0, loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ // Hardcode expected values taken from the test data file, assuming it
+ // won't change too often.
+ EXPECT_EQ(549, loader_->getSize());
+ EXPECT_EQ(549, loader_->getPosition());
+
+ checkBasicRRs();
+}
+
+// Test the $INCLUDE directive
+TEST_F(MasterLoaderTest, include) {
+ // Test various cases of include
+ const char* includes[] = {
+ "$include",
+ "$INCLUDE",
+ "$Include",
+ "$InCluDe",
+ "\"$INCLUDE\"",
+ NULL
+ };
+ for (const char** include = includes; *include != NULL; ++include) {
+ SCOPED_TRACE(*include);
+
+ clear();
+ // Prepare input source that has the include and some more data
+ // below (to see it returns back to the original source).
+ const string include_str = string(*include) + " " +
+ TEST_DATA_SRCDIR + "/example.org\nwww 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(include_str);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkBasicRRs();
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+ }
+}
+
+TEST_F(MasterLoaderTest, includeAndIncremental) {
+ // Check getSize() and getPosition() are adjusted before and after
+ // $INCLUDE.
+ const string first_rr = "before.example.org. 0 A 192.0.2.1\n";
+ const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org";
+ const string zone_data = first_rr + include_str + "\n" +
+ "www 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(zone_data);
+ setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT);
+
+ // On construction, getSize() returns the size of the data (exclude the
+ // the file to be included); position is set to 0.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ // Read the first RR. getSize() doesn't change; position should be
+ // at the end of the first line.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(first_rr.size(), loader_->getPosition());
+
+ // Read next 4. It includes $INCLUDE processing. Magic number of 549
+ // is the size of the test zone file (see above); 506 is the position in
+ // the file at the end of 4th RR (due to extra comments it's smaller than
+ // the file size).
+ loader_->loadIncremental(4);
+ EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+ EXPECT_EQ(first_rr.size() + include_str.size() + 506,
+ loader_->getPosition());
+
+ // Read the last one. At this point getSize and getPosition return
+ // the same value, indicating progress of 100%.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+ EXPECT_EQ(zone_data.size() + 549, loader_->getPosition());
+
+ // we were not interested in checking RRs in this test. clear them to
+ // not confuse TearDown().
+ rrsets_.clear();
+}
+
+// A commonly used helper to check callback message.
+void
+checkCallbackMessage(const string& actual_msg, const string& expected_msg,
+ size_t expected_line) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, actual_msg.find(expected_msg)) << "actual message: " <<
+ actual_msg << " expected: " <<
+ expected_msg;
+
+ // and it should end with "...:<line_num>]"
+ const string line_desc = ":" + lexical_cast<string>(expected_line) + "]";
+ EXPECT_EQ(actual_msg.size() - line_desc.size(),
+ actual_msg.find(line_desc)) << "Expected on line " <<
+ expected_line;
+}
+
+TEST_F(MasterLoaderTest, origin) {
+ // Various forms of the directive
+ const char* origins[] = {
+ "$origin",
+ "$ORIGIN",
+ "$Origin",
+ "$OrigiN",
+ "\"$ORIGIN\"",
+ NULL
+ };
+ for (const char** origin = origins; *origin != NULL; ++origin) {
+ SCOPED_TRACE(*origin);
+
+ clear();
+ const string directive = *origin;
+ const string input =
+ "@ 1H IN A 192.0.2.1\n" +
+ directive + " sub.example.org.\n"
+ "\"www\" 1H IN A 192.0.2.1\n" +
+ // Relative name in the origin
+ directive + " relative\n"
+ "@ 1H IN A 192.0.2.1\n"
+ // Origin is _not_ used here (absolute name)
+ "noorigin.example.org. 60M IN A 192.0.2.1\n";
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There's a relative origin in it, we warn about that.
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "The new origin is relative, did you really mean "
+ "relative.sub.example.org.?", 4);
+
+ checkARR("example.org");
+ checkARR("www.sub.example.org");
+ checkARR("relative.sub.example.org");
+ checkARR("noorigin.example.org");
+ }
+}
+
+// Test the source is correctly popped even after error
+TEST_F(MasterLoaderTest, popAfterError) {
+ const string include_str = "$include " TEST_DATA_SRCDIR
+ "/broken.zone\nwww 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(include_str);
+ // We perform the test with MANY_ERRORS, we want to see what happens
+ // after the error.
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size()); // For the broken RR
+ EXPECT_EQ(1, warnings_.size()); // For missing EOLN
+
+ // The included file doesn't contain anything usable, but the
+ // line after the include should be there.
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+}
+
+// Check it works the same when created based on a stream, not filename
+TEST_F(MasterLoaderTest, streamConstructor) {
+ const string zone_data(prepareZone("", true));
+ stringstream zone_stream(zone_data);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // Unlike the basicLoad test, if we construct the loader from a stream
+ // getSize() returns the data size in the stream immediately after the
+ // construction.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+
+ // On completion of the load, both getSize() and getPosition() return the
+ // size of the data.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(zone_data.size(), loader_->getPosition());
+}
+
+// Try loading data incrementally.
+TEST_F(MasterLoaderTest, incrementalLoad) {
+ setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_FALSE(loader_->loadIncremental(2));
+ EXPECT_FALSE(loader_->loadedSucessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkRR("example.org", RRType::SOA(),
+ "ns1.example.org. admin.example.org. "
+ "1234 3600 1800 2419200 7200");
+ checkRR("example.org", RRType::NS(), "ns1.example.org.");
+
+ // The third one is not loaded yet
+ EXPECT_TRUE(rrsets_.empty());
+
+ // Load the rest.
+ EXPECT_TRUE(loader_->loadIncremental(20));
+ EXPECT_TRUE(loader_->loadedSucessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ checkRR("www.example.org", RRType::A(), "192.0.2.1");
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+}
+
+// Try loading from file that doesn't exist. There should be single error
+// saying so.
+TEST_F(MasterLoaderTest, invalidFile) {
+ setLoader("This file doesn't exist at all",
+ Name("exmaple.org."), RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+ // Nothing yet. The loader is dormant until invoked.
+ // Is it really what we want?
+ EXPECT_TRUE(errors_.empty());
+
+ loader_->load();
+
+ EXPECT_TRUE(warnings_.empty());
+ EXPECT_TRUE(rrsets_.empty());
+ ASSERT_EQ(1, errors_.size());
+ EXPECT_EQ(0, errors_[0].find("Error opening the input source file: ")) <<
+ "Different error: " << errors_[0];
+}
+
+struct ErrorCase {
+ const char* const line; // The broken line in master file
+ const char* const reason; // If non NULL, the reason string
+ const char* const problem; // Description of the problem for SCOPED_TRACE
+} const error_cases[] = {
+ { "www... 3600 IN A 192.0.2.1", NULL, "Invalid name" },
+ { "www FORTNIGHT IN A 192.0.2.1", NULL, "Invalid TTL" },
+ { "www 3600 XX A 192.0.2.1", NULL, "Invalid class" },
+ { "www 3600 IN A bad_ip", NULL, "Invalid Rdata" },
+
+ // Parameter ordering errors
+ { "www IN A 3600 192.168.2.7",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Incorrect order of class, TTL and type" },
+ { "www A IN 3600 192.168.2.8",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Incorrect order of class, TTL and type" },
+ { "www 3600 A IN 192.168.2.7",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Incorrect order of class, TTL and type" },
+ { "www A 3600 IN 192.168.2.8",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Incorrect order of class, TTL and type" },
+
+ // Missing type and Rdata
+ { "www", "unexpected end of input", "Missing type and Rdata" },
+ { "www 3600", "unexpected end of input", "Missing type and Rdata" },
+ { "www IN", "unexpected end of input", "Missing type and Rdata" },
+ { "www 3600 IN", "unexpected end of input", "Missing type and Rdata" },
+ { "www IN 3600", "unexpected end of input", "Missing type and Rdata" },
+
+ // Missing Rdata
+ { "www A",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Missing Rdata" },
+ { "www 3600 A",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Missing Rdata" },
+ { "www IN A",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Missing Rdata" },
+ { "www 3600 IN A",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Missing Rdata" },
+ { "www IN 3600 A",
+ "createRdata from text failed: IN/A RDATA construction from text failed",
+ "Missing Rdata" },
+
+ { "www 3600 IN", NULL, "Unexpected EOLN" },
+ { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN",
+ "Class mismatch" },
+ { "www \"3600\" IN A 192.0.2.1", NULL, "Quoted TTL" },
+ { "www 3600 \"IN\" A 192.0.2.1", NULL, "Quoted class" },
+ { "www 3600 IN \"A\" 192.0.2.1", NULL, "Quoted type" },
+ { "unbalanced)paren 3600 IN A 192.0.2.1", NULL, "Token error 1" },
+ { "www 3600 unbalanced)paren A 192.0.2.1", NULL,
+ "Token error 2" },
+ // Check the unknown directive. The rest looks like ordinary RR,
+ // so we see the $ is actually special.
+ { "$UNKNOWN 3600 IN A 192.0.2.1", NULL, "Unknown $ directive" },
+ { "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Unknown directive 'INCLUD'",
+ "Include too short" },
+ { "$INCLUDES " TEST_DATA_SRCDIR "/example.org",
+ "Unknown directive 'INCLUDES'", "Include too long" },
+ { "$INCLUDE", "unexpected end of input", "Missing include path" },
+ // The following two error messages are system dependant, omitting
+ { "$INCLUDE /file/not/found", NULL, "Include file not found" },
+ { "$INCLUDE /file/not/found example.org. and here goes bunch of garbage",
+ NULL, "Include file not found and garbage at the end of line" },
+ { "$ORIGIN", "unexpected end of input", "Missing origin name" },
+ { "$ORIGIN invalid...name", "duplicate period in invalid...name",
+ "Invalid name for origin" },
+ { "$ORIGIN )brokentoken", "unbalanced parentheses",
+ "Broken token in origin" },
+ { "$ORIGIN example.org. garbage", "Extra tokens at the end of line",
+ "Garbage after origin" },
+ { "$ORIGI name.", "Unknown directive 'ORIGI'", "$ORIGIN too short" },
+ { "$ORIGINAL name.", "Unknown directive 'ORIGINAL'", "$ORIGIN too long" },
+ { "$TTL 100 extra-garbage", "Extra tokens at the end of line",
+ "$TTL with extra token" },
+ { "$TTL", "unexpected end of input", "missing TTL" },
+ { "$TTL No-ttl", "Unknown unit used: N in: No-ttl", "bad TTL" },
+ { "$TTL \"100\"", "invalid TTL: \"100\"", "bad TTL, quoted" },
+ { "$TT 100", "Unknown directive 'TT'", "bad directive, too short" },
+ { "$TTLLIKE 100", "Unknown directive 'TTLLIKE'", "bad directive, extra" },
+ { NULL, NULL, NULL }
+};
+
+// Test a broken zone is handled properly. We test several problems,
+// both in strict and lenient mode.
+TEST_F(MasterLoaderTest, brokenZone) {
+ for (const ErrorCase* ec = error_cases; ec->line != NULL; ++ec) {
+ SCOPED_TRACE(ec->problem);
+ const string zone(prepareZone(ec->line, true));
+
+ {
+ SCOPED_TRACE("Strict mode");
+ clear();
+ stringstream zone_stream(zone);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_THROW(loader_->load(), MasterLoaderError);
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size());
+ if (ec->reason != NULL) {
+ checkCallbackMessage(errors_.at(0), ec->reason, 2);
+ }
+ EXPECT_TRUE(warnings_.empty());
+
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ // In the strict mode, it is aborted. The last RR is not
+ // even attempted.
+ EXPECT_TRUE(rrsets_.empty());
+ }
+
+ {
+ SCOPED_TRACE("Lenient mode");
+ clear();
+ stringstream zone_stream(zone);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size());
+ EXPECT_TRUE(warnings_.empty());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ // This one is below the error one.
+ checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+ EXPECT_TRUE(rrsets_.empty());
+ }
+
+ {
+ SCOPED_TRACE("Error at EOF");
+ // This case is interesting only in the lenient mode.
+ clear();
+ const string zoneEOF(prepareZone(ec->line, false));
+ stringstream zone_stream(zoneEOF);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size()) << errors_[0] << "\n" << errors_[1];
+ // The unexpected EOF warning
+ EXPECT_EQ(1, warnings_.size());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ EXPECT_TRUE(rrsets_.empty());
+ }
+ }
+}
+
+// Check that a garbage after the include generates an error, but not fatal
+// one (in lenient mode) and we can recover.
+TEST_F(MasterLoaderTest, includeWithGarbage) {
+ // Include an origin (example.org) because we expect it to be handled
+ // soon and we don't want it to break here.
+ const string include_str("$INCLUDE " TEST_DATA_SRCDIR
+ "/example.org example.org. bunch of other stuff\n"
+ "www 3600 IN AAAA 2001:db8::1\n");
+ stringstream zone_stream(include_str);
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ ASSERT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
+ // It says something about extra tokens at the end
+ EXPECT_NE(string::npos, errors_[0].find("Extra"));
+ EXPECT_TRUE(warnings_.empty());
+ checkBasicRRs();
+ checkRR("www.example.org", RRType::AAAA(), "2001:db8::1");
+}
+
+// Check we error about garbage at the end of $ORIGIN line (but the line
+// works).
+TEST_F(MasterLoaderTest, originWithGarbage) {
+ const string origin_str = "$ORIGIN www.example.org. More garbage here\n"
+ "@ 1H IN A 192.0.2.1\n";
+ stringstream ss(origin_str);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ EXPECT_NO_THROW(loader_->load());
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ ASSERT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "Extra tokens at the end of line", 1);
+ EXPECT_TRUE(warnings_.empty());
+ checkARR("www.example.org");
+}
+
+// Test we can pass both file to include and the origin to switch
+TEST_F(MasterLoaderTest, includeAndOrigin) {
+ // First, switch origin to something else, so we can check it is
+ // switched back.
+ const string include_string = "$ORIGIN www.example.org.\n"
+ "@ 1H IN A 192.0.2.1\n"
+ // Then include the file with data and switch origin back
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org example.org.\n"
+ // Another RR to see we fall back to the previous origin.
+ "www 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+ checkBasicRRs();
+ checkARR("www.www.example.org");
+}
+
+// Like above, but the origin after include is bogus. The whole line should
+// be rejected.
+TEST_F(MasterLoaderTest, includeAndBadOrigin) {
+ const string include_string =
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org example..org.\n"
+ // Another RR to see the switch survives after we exit include
+ "www 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "duplicate period in example..org.",
+ 1);
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+}
+
+// Check the origin doesn't get outside of the included file.
+TEST_F(MasterLoaderTest, includeOriginRestore) {
+ const string include_string =
+ "$INCLUDE " TEST_DATA_SRCDIR "/origincheck.txt\n"
+ "@ 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+ // And check it's the correct data
+ checkARR("www.example.org");
+ checkARR("example.org");
+}
+
+// Check we restore the last name for initial whitespace when returning from
+// include. But we do produce a warning if there's one just ofter the include.
+TEST_F(MasterLoaderTest, includeAndInitialWS) {
+ const string include_string = "xyz 1H IN A 192.0.2.1\n"
+ "$INCLUDE " TEST_DATA_SRCDIR "/example.org\n"
+ " 1H IN A 192.0.2.1\n";
+ stringstream ss(include_string);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ // Successfully load the data
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "Owner name omitted around $INCLUDE, the result might "
+ "not be as expected", 3);
+ checkARR("xyz.example.org");
+ checkBasicRRs();
+ checkARR("xyz.example.org");
+}
+
+// Test for "$TTL"
+TEST_F(MasterLoaderTest, ttlDirective) {
+ stringstream zone_stream;
+
+ // Set the default TTL with $TTL followed by an RR omitting the TTL
+ zone_stream << "$TTL 1800\nexample.org. IN A 192.0.2.1\n";
+ // $TTL can be quoted. Also testing the case of $TTL being changed.
+ zone_stream << "\"$TTL\" 100\na.example.org. IN A 192.0.2.2\n";
+ // Extended TTL form is accepted.
+ zone_stream << "$TTL 1H\nb.example.org. IN A 192.0.2.3\n";
+ // Matching is case insensitive.
+ zone_stream << "$tTl 360\nc.example.org. IN A 192.0.2.4\n";
+ // Maximum allowable TTL
+ zone_stream << "$TTL 2147483647\nd.example.org. IN A 192.0.2.5\n";
+
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ checkRR("example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("a.example.org", RRType::A(), "192.0.2.2", RRTTL(100));
+ checkRR("b.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
+ checkRR("c.example.org", RRType::A(), "192.0.2.4", RRTTL(360));
+ checkRR("d.example.org", RRType::A(), "192.0.2.5", RRTTL(2147483647));
+}
+
+TEST_F(MasterLoaderTest, ttlFromSOA) {
+ // No $TTL, and the SOA doesn't have an explicit TTL field. Its minimum
+ // TTL field will be used as the RR's TTL, and it'll be used as the
+ // default TTL for others.
+ stringstream zone_stream("example.org. IN SOA . . 0 0 0 0 1800\n"
+ "a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(1800));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+
+ // The use of SOA minimum TTL should have caused a warning.
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0),
+ "no TTL specified; using SOA MINTTL instead", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPrevious) {
+ // No available default TTL. 2nd and 3rd RR will use the TTL of the
+ // 1st RR. This will result in a warning, but only for the first time.
+ stringstream zone_stream("a.example.org. 1800 IN A 192.0.2.1\n"
+ "b.example.org. IN A 192.0.2.2\n"
+ "c.example.org. IN A 192.0.2.3\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+ checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(1800));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, RRParamsOrdering) {
+ // We test the order and existence of TTL, class and type. See
+ // MasterLoader::MasterLoaderImpl::parseRRParams() for ordering.
+
+ stringstream zone_stream;
+ // <TTL> <class> <type> <RDATA>
+ zone_stream << "a.example.org. 1800 IN A 192.0.2.1\n";
+ // <type> <RDATA>
+ zone_stream << "b.example.org. A 192.0.2.2\n";
+ // <class> <TTL> <type> <RDATA>
+ zone_stream << "c.example.org. IN 3600 A 192.0.2.3\n";
+ // <TTL> <type> <RDATA>
+ zone_stream << "d.example.org. 7200 A 192.0.2.4\n";
+ // <class> <type> <RDATA>
+ zone_stream << "e.example.org. IN A 192.0.2.5\n";
+
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(1800));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+ checkRR("c.example.org", RRType::A(), "192.0.2.3", RRTTL(3600));
+ checkRR("d.example.org", RRType::A(), "192.0.2.4", RRTTL(7200));
+ checkRR("e.example.org", RRType::A(), "192.0.2.5", RRTTL(7200));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlFromPreviousSOA) {
+ // Mixture of the previous two cases: SOA has explicit TTL, followed by
+ // an RR without an explicit TTL. In this case the minimum TTL won't be
+ // recognized as the "default TTL".
+ stringstream zone_stream("example.org. 100 IN SOA . . 0 0 0 0 1800\n"
+ "a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 1800", RRTTL(100));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(100));
+
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "using RFC1035 TTL semantics", 2);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknown) {
+ // No available TTL is known for the first RR.
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+ EXPECT_THROW(loader_->load(), MasterLoaderError);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndContinue) {
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1\n"
+ "b.example.org. 1800 IN A 192.0.2.2\n");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(1800));
+
+ EXPECT_TRUE(warnings_.empty());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+}
+
+TEST_F(MasterLoaderTest, ttlUnknownAndEOF) {
+ // Similar to the previous case, but the input will be abruptly terminated
+ // after the offending RR. This will cause an additional warning.
+ stringstream zone_stream("a.example.org. IN A 192.0.2.1");
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_TRUE(rrsets_.empty());
+
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "no TTL specified; load rejected", 1);
+
+ // RDATA implementation can complain about it, too. To be independent of
+ // its details, we focus on the very last warning.
+ EXPECT_FALSE(warnings_.empty());
+ checkCallbackMessage(*warnings_.rbegin(), "File does not end with newline",
+ 1);
+}
+
+TEST_F(MasterLoaderTest, ttlOverflow) {
+ stringstream zone_stream;
+ zone_stream << "example.org. IN SOA . . 0 0 0 0 2147483648\n";
+ zone_stream << "$TTL 3600\n"; // reset to an in-range value
+ zone_stream << "$TTL 2147483649\n" << "a.example.org. IN A 192.0.2.1\n";
+ zone_stream << "$TTL 3600\n"; // reset to an in-range value
+ zone_stream << "b.example.org. 2147483650 IN A 192.0.2.2\n";
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::DEFAULT);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_EQ(3, rrsets_.size());
+
+ checkRR("example.org", RRType::SOA(), ". . 0 0 0 0 2147483648", RRTTL(0));
+ checkRR("a.example.org", RRType::A(), "192.0.2.1", RRTTL(0));
+ checkRR("b.example.org", RRType::A(), "192.0.2.2", RRTTL(0));
+
+ EXPECT_EQ(4, warnings_.size());
+ checkCallbackMessage(warnings_.at(1),
+ "TTL 2147483648 > MAXTTL, setting to 0 per RFC2181",
+ 1);
+ checkCallbackMessage(warnings_.at(2),
+ "TTL 2147483649 > MAXTTL, setting to 0 per RFC2181",
+ 3);
+ checkCallbackMessage(warnings_.at(3),
+ "TTL 2147483650 > MAXTTL, setting to 0 per RFC2181",
+ 6);
+}
+
+// Test the constructor rejects empty add callback.
+TEST_F(MasterLoaderTest, emptyCallback) {
+ EXPECT_THROW(MasterLoader(TEST_DATA_SRCDIR "/example.org",
+ Name("example.org"), RRClass::IN(), callbacks_,
+ AddRRCallback()), isc::InvalidParameter);
+ // And the same with the second constructor
+ stringstream ss("");
+ EXPECT_THROW(MasterLoader(ss, Name("example.org"), RRClass::IN(),
+ callbacks_, AddRRCallback()),
+ isc::InvalidParameter);
+}
+
+// Check it throws when we try to load after loading was complete.
+TEST_F(MasterLoaderTest, loadTwice) {
+ setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_THROW(loader_->load(), isc::InvalidOperation);
+ // Don't check them, they are not interesting, so suppress the error
+ // at TearDown
+ rrsets_.clear();
+}
+
+// Load 0 items should be rejected
+TEST_F(MasterLoaderTest, loadZero) {
+ setLoader(TEST_DATA_SRCDIR "/example.org", Name("example.org."),
+ RRClass::IN(), MasterLoader::MANY_ERRORS);
+ EXPECT_THROW(loader_->loadIncremental(0), isc::InvalidParameter);
+}
+
+// Test there's a warning when the file terminates without end of
+// line.
+TEST_F(MasterLoaderTest, noEOLN) {
+ // No \n at the end
+ const string input("example.org. 3600 IN SOA ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There should be one warning about the EOLN
+ EXPECT_EQ(1, warnings_.size());
+ checkRR("example.org", RRType::SOA(), "ns1.example.org. "
+ "admin.example.org. 1234 3600 1800 2419200 7200");
+}
+
+// Test it rejects when we don't have the previous name to use in place of
+// initial whitespace
+TEST_F(MasterLoaderTest, noPreviousName) {
+ const string input(" 1H IN A 192.0.2.1\n");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ EXPECT_EQ(1, errors_.size());
+ checkCallbackMessage(errors_.at(0), "No previous name to use in place of "
+ "initial whitespace", 1);
+ EXPECT_TRUE(warnings_.empty());
+}
+
+// Check we warn if the first RR in an included file has omitted name
+TEST_F(MasterLoaderTest, previousInInclude) {
+ const string input("www 1H IN A 192.0.2.1\n"
+ "$INCLUDE " TEST_DATA_SRCDIR "/omitcheck.txt\n");
+ stringstream ss(input);
+ setLoader(ss, Name("example.org"), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+ EXPECT_TRUE(errors_.empty());
+ // There should be one warning about the EOLN
+ EXPECT_EQ(1, warnings_.size());
+ checkCallbackMessage(warnings_.at(0), "Owner name omitted around "
+ "$INCLUDE, the result might not be as expected", 1);
+ checkARR("www.example.org");
+ checkARR("www.example.org");
+}
+
+}
diff --git a/src/lib/dns/tests/rdata_afsdb_unittest.cc b/src/lib/dns/tests/rdata_afsdb_unittest.cc
index 521bec5..9bb64b7 100644
--- a/src/lib/dns/tests/rdata_afsdb_unittest.cc
+++ b/src/lib/dns/tests/rdata_afsdb_unittest.cc
@@ -114,6 +114,16 @@ TEST_F(Rdata_AFSDB_Test, createFromWire) {
DNSMessageFORMERR);
}
+TEST_F(Rdata_AFSDB_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_afsdb.compare(
+ *test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ afsdb_text)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::AFSDB(), RRClass::IN(),
+ "1root.example.com."));
+}
+
TEST_F(Rdata_AFSDB_Test, toWireBuffer) {
// construct actual data
rdata_afsdb.toWire(obuffer);
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
new file mode 100644
index 0000000..3059669
--- /dev/null
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -0,0 +1,252 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <util/unittests/wiredata.h>
+
+#include <dns/exceptions.h>
+#include <dns/rdata.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <string>
+#include <vector>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using isc::dns::rdata::generic::detail::CharString;
+using isc::dns::rdata::generic::detail::bufferToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
+using isc::dns::rdata::generic::detail::compareCharStrings;
+using isc::util::unittests::matchWireData;
+
+namespace {
+const uint8_t test_charstr[] = {
+ sizeof("Test String") - 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+};
+
+class CharStringTest : public ::testing::Test {
+protected:
+ CharStringTest() :
+ // char-string representation for test data using two types of escape
+ // ('r' = 114)
+ test_str("Test\\ St\\114ing")
+ {
+ str_region.beg = &test_str[0];
+ str_region.len = test_str.size();
+ }
+ CharString chstr; // place holder
+ const std::string test_str;
+ MasterToken::StringRegion str_region;
+};
+
+MasterToken::StringRegion
+createStringRegion(const std::string& str) {
+ MasterToken::StringRegion region;
+ region.beg = &str[0]; // note std ensures this works even if str is empty
+ region.len = str.size();
+ return (region);
+}
+
+TEST_F(CharStringTest, normalConversion) {
+ uint8_t tmp[3]; // placeholder for expected sequence
+
+ stringToCharString(str_region, chstr);
+ matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
+
+ // Empty string
+ chstr.clear();
+ stringToCharString(createStringRegion(""), chstr);
+ tmp[0] = 0;
+ matchWireData(tmp, 1, &chstr[0], chstr.size());
+
+ // Possible largest char string
+ chstr.clear();
+ std::string long_str(255, 'x');
+ stringToCharString(createStringRegion(long_str), chstr);
+ std::vector<uint8_t> expected;
+ expected.push_back(255); // len of char string
+ expected.insert(expected.end(), long_str.begin(), long_str.end());
+ matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+ // Same data as the previous case, but the original string is longer than
+ // the max; this shouldn't be rejected
+ chstr.clear();
+ long_str.at(254) = '\\'; // replace the last 'x' with '\'
+ long_str.append("120"); // 'x' = 120
+ stringToCharString(createStringRegion(long_str), chstr);
+ matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
+
+ // Escaped '\'
+ chstr.clear();
+ tmp[0] = 1;
+ tmp[1] = '\\';
+ stringToCharString(createStringRegion("\\\\"), chstr);
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ // Boundary values for \DDD
+ chstr.clear();
+ tmp[0] = 1;
+ tmp[1] = 0;
+ stringToCharString(createStringRegion("\\000"), chstr);
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ chstr.clear();
+ stringToCharString(createStringRegion("\\255"), chstr);
+ tmp[0] = 1;
+ tmp[1] = 255;
+ matchWireData(tmp, 2, &chstr[0], chstr.size());
+
+ // Another digit follows DDD; it shouldn't cause confusion
+ chstr.clear();
+ stringToCharString(createStringRegion("\\2550"), chstr);
+ tmp[0] = 2; // string len is now 2
+ tmp[2] = '0';
+ matchWireData(tmp, 3, &chstr[0], chstr.size());
+}
+
+TEST_F(CharStringTest, badConversion) {
+ // string cannot exceed 255 bytes
+ EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+ chstr),
+ CharStringTooLong);
+
+ // input string ending with (non escaped) '\'
+ chstr.clear();
+ EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
+ InvalidRdataText);
+}
+
+TEST_F(CharStringTest, badDDD) {
+ // Check various type of bad form of \DDD
+
+ // Not a number
+ EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
+ InvalidRdataText);
+ EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
+ InvalidRdataText);
+
+ // Not in the range of uint8_t
+ EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
+ InvalidRdataText);
+
+ // Short buffer
+ EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
+ InvalidRdataText);
+}
+
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ assert(sizeof(idata) > length);
+ idata[0] = static_cast<uint8_t>(length);
+ std::memcpy(idata + 1, cur->data, length);
+ const CharString test_data(idata, idata + length + 1);
+ EXPECT_EQ(cur->expected, charStringToString(test_data));
+ }
+}
+
+TEST_F(CharStringTest, bufferToCharString) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ size_t read = bufferToCharString(buf, chstr_size, chstr);
+
+ EXPECT_EQ(chstr_size, read);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+}
+
+TEST_F(CharStringTest, bufferToCharString_bad) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ // Set valid data in both so we can make sure the charstr is not
+ // modified
+ bufferToCharString(buf, chstr_size, chstr);
+ ASSERT_EQ("Test String", charStringToString(chstr));
+
+ // Should be at end of buffer now, so it should fail
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // reset and try to read with too low rdata_len
+ buf.setPosition(0);
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // set internal charstring len too high
+ const uint8_t test_charstr_err[] = {
+ sizeof("Test String") + 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+ };
+ buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err));
+ EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+}
+
+
+
+TEST_F(CharStringTest, compareCharString) {
+ CharString charstr;
+ CharString charstr2;
+ CharString charstr_small1;
+ CharString charstr_small2;
+ CharString charstr_large1;
+ CharString charstr_large2;
+ CharString charstr_empty;
+
+ stringToCharString(createStringRegion("test string"), charstr);
+ stringToCharString(createStringRegion("test string"), charstr2);
+ stringToCharString(createStringRegion("test strin"), charstr_small1);
+ stringToCharString(createStringRegion("test strina"), charstr_small2);
+ stringToCharString(createStringRegion("test stringa"), charstr_large1);
+ stringToCharString(createStringRegion("test strinz"), charstr_large2);
+
+ EXPECT_EQ(0, compareCharStrings(charstr, charstr2));
+ EXPECT_EQ(0, compareCharStrings(charstr2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr));
+
+ EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty));
+ EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty));
+}
+
+} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_cname_unittest.cc b/src/lib/dns/tests/rdata_cname_unittest.cc
index 2cce9bc..6451f72 100644
--- a/src/lib/dns/tests/rdata_cname_unittest.cc
+++ b/src/lib/dns/tests/rdata_cname_unittest.cc
@@ -87,6 +87,12 @@ TEST_F(Rdata_CNAME_Test, createFromWire) {
InvalidRdataLength);
}
+TEST_F(Rdata_CNAME_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_cname.compare(
+ *test::createRdataUsingLexer(RRType::CNAME(), RRClass::IN(),
+ "cn.example.com")));
+}
+
TEST_F(Rdata_CNAME_Test, toWireBuffer) {
rdata_cname.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_dhcid_unittest.cc b/src/lib/dns/tests/rdata_dhcid_unittest.cc
index 38b1459..8d56c0e 100644
--- a/src/lib/dns/tests/rdata_dhcid_unittest.cc
+++ b/src/lib/dns/tests/rdata_dhcid_unittest.cc
@@ -63,6 +63,16 @@ TEST_F(Rdata_DHCID_Test, createFromWire) {
// TBD: more tests
}
+TEST_F(Rdata_DHCID_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_dhcid.compare(
+ *test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
+ string_dhcid)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::DHCID(), RRClass::IN(),
+ "00"));
+}
+
TEST_F(Rdata_DHCID_Test, toWireRenderer) {
rdata_dhcid.toWire(renderer);
diff --git a/src/lib/dns/tests/rdata_dname_unittest.cc b/src/lib/dns/tests/rdata_dname_unittest.cc
index eb221d8..c4e517c 100644
--- a/src/lib/dns/tests/rdata_dname_unittest.cc
+++ b/src/lib/dns/tests/rdata_dname_unittest.cc
@@ -89,6 +89,12 @@ TEST_F(Rdata_DNAME_Test, createFromWire) {
InvalidRdataLength);
}
+TEST_F(Rdata_DNAME_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_dname.compare(
+ *test::createRdataUsingLexer(RRType::DNAME(), RRClass::IN(),
+ "dn.example.com")));
+}
+
TEST_F(Rdata_DNAME_Test, toWireBuffer) {
rdata_dname.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_dnskey_unittest.cc b/src/lib/dns/tests/rdata_dnskey_unittest.cc
index 86b8f69..58d29bf 100644
--- a/src/lib/dns/tests/rdata_dnskey_unittest.cc
+++ b/src/lib/dns/tests/rdata_dnskey_unittest.cc
@@ -82,6 +82,17 @@ TEST_F(Rdata_DNSKEY_Test, DISABLED_badText) {
InvalidRdataText);
}
+TEST_F(Rdata_DNSKEY_Test, createFromLexer) {
+ generic::DNSKEY rdata_dnskey(dnskey_txt);
+ EXPECT_EQ(0, rdata_dnskey.compare(
+ *test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
+ dnskey_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::DNSKEY(), RRClass::IN(),
+ "257 3 5"));
+}
+
TEST_F(Rdata_DNSKEY_Test, toWireRenderer) {
renderer.skip(2);
generic::DNSKEY rdata_dnskey(dnskey_txt);
diff --git a/src/lib/dns/tests/rdata_ds_like_unittest.cc b/src/lib/dns/tests/rdata_ds_like_unittest.cc
index 6172431..28a2e17 100644
--- a/src/lib/dns/tests/rdata_ds_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_ds_like_unittest.cc
@@ -85,6 +85,16 @@ TYPED_TEST(Rdata_DS_LIKE_Test, createFromWire_DS_LIKE) {
"rdata_ds_fromWire")));
}
+TYPED_TEST(Rdata_DS_LIKE_Test, createFromLexer_DS_LIKE) {
+ EXPECT_EQ(0, this->rdata_ds_like.compare(
+ *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ ds_like_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "99999 5 2 BEEF"));
+}
+
TYPED_TEST(Rdata_DS_LIKE_Test, assignment_DS_LIKE) {
TypeParam copy((string(ds_like_txt)));
copy = this->rdata_ds_like;
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index c934a4f..7be2cb6 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -41,6 +41,7 @@ static uint8_t hinfo_rdata[] = {0x07,0x50,0x65,0x6e,0x74,0x69,0x75,0x6d,0x05,
static const char *hinfo_str = "\"Pentium\" \"Linux\"";
static const char *hinfo_str1 = "\"Pen\\\"tium\" \"Linux\"";
+static const char *hinfo_str_equal = "\"Pentium\"\"Linux\"";
static const char *hinfo_str_small1 = "\"Lentium\" \"Linux\"";
static const char *hinfo_str_small2 = "\"Pentium\" \"Kinux\"";
static const char *hinfo_str_large1 = "\"Qentium\" \"Linux\"";
@@ -50,15 +51,17 @@ TEST_F(Rdata_HINFO_Test, createFromText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(string("Pentium"), hinfo.getCPU());
EXPECT_EQ(string("Linux"), hinfo.getOS());
-
// Test the text with double quotes in the middle of string
HINFO hinfo1(hinfo_str1);
- EXPECT_EQ(string("Pen\"tium"), hinfo1.getCPU());
+ EXPECT_EQ(string("Pen\\\"tium"), hinfo1.getCPU());
}
TEST_F(Rdata_HINFO_Test, badText) {
- // Fields must be seperated by spaces
- EXPECT_THROW(const HINFO hinfo("\"Pentium\"\"Linux\""), InvalidRdataText);
+ // Only 2 fields must exist
+ EXPECT_THROW(const HINFO hinfo("\"Pentium\"\"Linux\"\"Computer\""),
+ InvalidRdataText);
+ EXPECT_THROW(const HINFO hinfo("\"Pentium\" \"Linux\" \"Computer\""),
+ InvalidRdataText);
// Field cannot be missing
EXPECT_THROW(const HINFO hinfo("Pentium"), InvalidRdataText);
// The <character-string> cannot exceed 255 characters
@@ -77,9 +80,35 @@ TEST_F(Rdata_HINFO_Test, createFromWire) {
EXPECT_EQ(string("Linux"), hinfo.getOS());
}
+TEST_F(Rdata_HINFO_Test, createFromLexer) {
+ HINFO rdata_hinfo(hinfo_str);
+ EXPECT_EQ(0, rdata_hinfo.compare(
+ *test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ hinfo_str)));
+ EXPECT_EQ(0, rdata_hinfo.compare(
+ *test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\"\"Linux\"")));
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\"\"Linux\""
+ "\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\" \"Linux\" "
+ "\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\""));
+}
+
TEST_F(Rdata_HINFO_Test, toText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(hinfo_str, hinfo.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("\"a\" \"b\"", HINFO("a b").toText());
+ // will not add additional quotes
+ EXPECT_EQ("\"a\" \"b\"", HINFO("\"a\" \"b\"").toText());
+ // And make sure escaped quotes and spaces are left intact
+ EXPECT_EQ("\"a\\\"\" \"b c\"", HINFO("\"a\\\"\" \"b c\"").toText());
}
TEST_F(Rdata_HINFO_Test, toWire) {
@@ -107,10 +136,24 @@ TEST_F(Rdata_HINFO_Test, compare) {
HINFO hinfo_large2(hinfo_str_large2);
EXPECT_EQ(0, hinfo.compare(HINFO(hinfo_str)));
+ EXPECT_EQ(0, hinfo.compare(HINFO(hinfo_str_equal)));
EXPECT_EQ(1, hinfo.compare(HINFO(hinfo_str_small1)));
EXPECT_EQ(1, hinfo.compare(HINFO(hinfo_str_small2)));
EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large1)));
EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large2)));
}
+// Copy/assign test
+TEST_F(Rdata_HINFO_Test, copy) {
+ HINFO hinfo(hinfo_str);
+ HINFO hinfo2(hinfo);
+ HINFO hinfo3 = hinfo;
+
+ EXPECT_EQ(0, hinfo.compare(hinfo2));
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+
+ hinfo3 = hinfo;
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_in_a_unittest.cc b/src/lib/dns/tests/rdata_in_a_unittest.cc
index 2fea9a3..7ab3423 100644
--- a/src/lib/dns/tests/rdata_in_a_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_a_unittest.cc
@@ -68,6 +68,11 @@ TEST_F(Rdata_IN_A_Test, createFromWire) {
DNSMessageFORMERR);
}
+TEST_F(Rdata_IN_A_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_in_a.compare(
+ *test::createRdataUsingLexer(RRType::A(), RRClass::IN(), "192.0.2.1")));
+}
+
TEST_F(Rdata_IN_A_Test, toWireBuffer) {
rdata_in_a.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
index d8ed1d6..74a3e3f 100644
--- a/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
+++ b/src/lib/dns/tests/rdata_in_aaaa_unittest.cc
@@ -66,6 +66,12 @@ TEST_F(Rdata_IN_AAAA_Test, createFromWire) {
DNSMessageFORMERR);
}
+TEST_F(Rdata_IN_AAAA_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_in_aaaa.compare(
+ *test::createRdataUsingLexer(RRType::AAAA(), RRClass::IN(),
+ "2001:db8::1234")));
+}
+
TEST_F(Rdata_IN_AAAA_Test, toWireBuffer) {
rdata_in_aaaa.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_minfo_unittest.cc b/src/lib/dns/tests/rdata_minfo_unittest.cc
index 78e8325..2f717fe 100644
--- a/src/lib/dns/tests/rdata_minfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_minfo_unittest.cc
@@ -103,6 +103,12 @@ TEST_F(Rdata_MINFO_Test, createFromWire) {
DNSMessageFORMERR);
}
+TEST_F(Rdata_MINFO_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_minfo.compare(
+ *test::createRdataUsingLexer(RRType::MINFO(), RRClass::IN(),
+ minfo_txt)));
+}
+
TEST_F(Rdata_MINFO_Test, assignment) {
generic::MINFO copy((string(minfo_txt2)));
copy = rdata_minfo;
diff --git a/src/lib/dns/tests/rdata_mx_unittest.cc b/src/lib/dns/tests/rdata_mx_unittest.cc
index 7dc774d..6c6039a 100644
--- a/src/lib/dns/tests/rdata_mx_unittest.cc
+++ b/src/lib/dns/tests/rdata_mx_unittest.cc
@@ -62,6 +62,16 @@ TEST_F(Rdata_MX_Test, createFromWire) {
// TBD: more tests
}
+TEST_F(Rdata_MX_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_mx.compare(
+ *test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx.example.com")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::MX(), RRClass::IN(),
+ "10 mx. example.com"));
+}
+
TEST_F(Rdata_MX_Test, toWireRenderer) {
renderer.writeName(Name("example.com"));
rdata_mx.toWire(renderer);
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index 5abcaef..d828e73 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -99,10 +99,12 @@ TEST_F(Rdata_NAPTR_Test, badText) {
// Order or preference cannot be missed
EXPECT_THROW(const NAPTR naptr("10 \"S\" SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- // Fields must be seperated by spaces
+ // Unquoted fields must be seperated by spaces
EXPECT_THROW(const NAPTR naptr("100 10S SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- EXPECT_THROW(const NAPTR naptr("100 10 \"S\"\"SIP\" \"\" _sip._udp.example.com."),
+ EXPECT_THROW(const NAPTR naptr("10010 \"S\" \"SIP\" \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(const NAPTR naptr("100 10 SSIP \"\" _sip._udp.example.com."),
InvalidRdataText);
// Field cannot be missing
EXPECT_THROW(const NAPTR naptr("100 10 \"S\""), InvalidRdataText);
@@ -128,6 +130,39 @@ TEST_F(Rdata_NAPTR_Test, createFromWire) {
EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
}
+TEST_F(Rdata_NAPTR_Test, createFromWireTooLongDataLen) {
+ static uint8_t naptr_rdata_long[] = {
+ 0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,0x50,0x2b,0x44,0x32,0x55,
+ 0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,0x70,0x07,0x65,0x78,
+ 0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00,0xff,0xff,0xff,0xff};
+ InputBuffer input_buffer(naptr_rdata_long, sizeof(naptr_rdata_long));
+ EXPECT_THROW(NAPTR naptr(input_buffer, sizeof(naptr_rdata_long)),
+ isc::dns::DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooShortDataLen) {
+ // missing data (just set rdata_len too low)
+ for (size_t i = 0; i < sizeof(naptr_rdata); ++i) {
+ // Just use existing correct buffer but set rdata_len too low
+ InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+ EXPECT_THROW(NAPTR naptr(input_buffer, i),
+ isc::dns::DNSMessageFORMERR);
+ }
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromLexer) {
+ const NAPTR rdata_naptr(naptr_str);
+
+ EXPECT_EQ(0, rdata_naptr.compare(
+ *test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+ naptr_str)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NAPTR(), RRClass::IN(),
+ "65536 10 S SIP \"\" "
+ "_sip._udp.example.com."));
+}
+
TEST_F(Rdata_NAPTR_Test, toWire) {
NAPTR naptr(naptr_str);
naptr.toWire(obuffer);
@@ -148,6 +183,14 @@ TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
TEST_F(Rdata_NAPTR_Test, toText) {
NAPTR naptr(naptr_str);
EXPECT_EQ(naptr_str, naptr.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 S SIP+D2U .* _sip._udp.example.com.").toText());
+ // will not add additional quotes
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.")
+ .toText());
}
TEST_F(Rdata_NAPTR_Test, compare) {
@@ -164,16 +207,38 @@ TEST_F(Rdata_NAPTR_Test, compare) {
NAPTR naptr_large5(naptr_str_large5);
EXPECT_EQ(0, naptr.compare(NAPTR(naptr_str)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small1)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small2)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small3)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small4)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small5)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large1)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large2)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large3)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large4)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large5)));
+ EXPECT_EQ(1, naptr.compare(naptr_small1));
+ EXPECT_EQ(1, naptr.compare(naptr_small2));
+ EXPECT_EQ(1, naptr.compare(naptr_small3));
+ EXPECT_EQ(1, naptr.compare(naptr_small4));
+ EXPECT_EQ(1, naptr.compare(naptr_small5));
+ EXPECT_EQ(-1, naptr.compare(naptr_large1));
+ EXPECT_EQ(-1, naptr.compare(naptr_large2));
+ EXPECT_EQ(-1, naptr.compare(naptr_large3));
+ EXPECT_EQ(-1, naptr.compare(naptr_large4));
+ EXPECT_EQ(-1, naptr.compare(naptr_large5));
+ EXPECT_EQ(-1, naptr_small1.compare(naptr));
+ EXPECT_EQ(-1, naptr_small2.compare(naptr));
+ EXPECT_EQ(-1, naptr_small3.compare(naptr));
+ EXPECT_EQ(-1, naptr_small4.compare(naptr));
+ EXPECT_EQ(-1, naptr_small5.compare(naptr));
+ EXPECT_EQ(1, naptr_large1.compare(naptr));
+ EXPECT_EQ(1, naptr_large2.compare(naptr));
+ EXPECT_EQ(1, naptr_large3.compare(naptr));
+ EXPECT_EQ(1, naptr_large4.compare(naptr));
+ EXPECT_EQ(1, naptr_large5.compare(naptr));
+}
+
+TEST_F(Rdata_NAPTR_Test, copy) {
+ NAPTR naptr(naptr_str);
+ NAPTR naptr2(naptr);
+ NAPTR naptr3 = naptr;
+
+ EXPECT_EQ(0, naptr.compare(naptr2));
+ EXPECT_EQ(0, naptr.compare(naptr3));
+
+ naptr3 = naptr;
+ EXPECT_EQ(0, naptr.compare(naptr3));
}
}
diff --git a/src/lib/dns/tests/rdata_ns_unittest.cc b/src/lib/dns/tests/rdata_ns_unittest.cc
index 47582ce..d536393 100644
--- a/src/lib/dns/tests/rdata_ns_unittest.cc
+++ b/src/lib/dns/tests/rdata_ns_unittest.cc
@@ -86,6 +86,16 @@ TEST_F(Rdata_NS_Test, createFromWire) {
InvalidRdataLength);
}
+TEST_F(Rdata_NS_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_ns.compare(
+ *test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ "ns.example.com")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NS(), RRClass::IN(),
+ ""));
+}
+
TEST_F(Rdata_NS_Test, toWireBuffer) {
rdata_ns.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_nsec3_unittest.cc b/src/lib/dns/tests/rdata_nsec3_unittest.cc
index edd2d4b..0fec3eb 100644
--- a/src/lib/dns/tests/rdata_nsec3_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3_unittest.cc
@@ -130,6 +130,18 @@ TEST_F(Rdata_NSEC3_Test, createFromWire) {
}
}
+TEST_F(Rdata_NSEC3_Test, createFromLexer) {
+ const generic::NSEC3 rdata_nsec3(nsec3_txt);
+ EXPECT_EQ(0, rdata_nsec3.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ nsec3_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC3(), RRClass::IN(),
+ "1 1 1 ADDAFEEE CPNMU=== "
+ "A NS SOA"));
+}
+
TEST_F(Rdata_NSEC3_Test, assign) {
generic::NSEC3 rdata_nsec3(nsec3_txt);
generic::NSEC3 other_nsec3 = rdata_nsec3;
diff --git a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
index 51fef01..23d6d0e 100644
--- a/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_like_unittest.cc
@@ -208,6 +208,17 @@ TYPED_TEST(NSEC3PARAMLikeTest, createFromWire) {
EXPECT_EQ(0, this->convert(*rdata).getSalt().size());
}
+TYPED_TEST(NSEC3PARAMLikeTest, createFromLexer) {
+ EXPECT_EQ(0, this->fromText(this->salt_txt).compare(
+ *test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+ this->salt_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(this->getType(), RRClass::IN(),
+ "1000000 1 1 ADDAFEEE" +
+ this->getCommonText()));
+}
+
template <typename OUTPUT_TYPE>
void
toWireCheck(RRType rrtype, OUTPUT_TYPE& output, const string& data_file) {
diff --git a/src/lib/dns/tests/rdata_nsec3param_unittest.cc b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
index 7558b42..115d3d3 100644
--- a/src/lib/dns/tests/rdata_nsec3param_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec3param_unittest.cc
@@ -86,6 +86,13 @@ TEST_F(Rdata_NSEC3PARAM_Test, createFromWire) {
}
}
+TEST_F(Rdata_NSEC3PARAM_Test, createFromLexer) {
+ const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
+ EXPECT_EQ(0, rdata_nsec3param.compare(
+ *test::createRdataUsingLexer(RRType::NSEC3PARAM(), RRClass::IN(),
+ nsec3param_txt)));
+}
+
TEST_F(Rdata_NSEC3PARAM_Test, toWireRenderer) {
renderer.skip(2);
const generic::NSEC3PARAM rdata_nsec3param(nsec3param_txt);
diff --git a/src/lib/dns/tests/rdata_nsec_unittest.cc b/src/lib/dns/tests/rdata_nsec_unittest.cc
index 88c6201..4092c6d 100644
--- a/src/lib/dns/tests/rdata_nsec_unittest.cc
+++ b/src/lib/dns/tests/rdata_nsec_unittest.cc
@@ -66,6 +66,17 @@ TEST_F(Rdata_NSEC_Test, createFromWire_NSEC) {
// Invalid bitmap cases are tested in Rdata_NSECBITMAP_Test.
}
+TEST_F(Rdata_NSEC_Test, createFromLexer_NSEC) {
+ const generic::NSEC rdata_nsec(nsec_txt);
+ EXPECT_EQ(0, rdata_nsec.compare(
+ *test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+ nsec_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::NSEC(), RRClass::IN(),
+ "www.isc.org."));
+}
+
TEST_F(Rdata_NSEC_Test, toWireRenderer_NSEC) {
renderer.skip(2);
const generic::NSEC rdata_nsec(nsec_txt);
diff --git a/src/lib/dns/tests/rdata_opt_unittest.cc b/src/lib/dns/tests/rdata_opt_unittest.cc
index 698d586..5699259 100644
--- a/src/lib/dns/tests/rdata_opt_unittest.cc
+++ b/src/lib/dns/tests/rdata_opt_unittest.cc
@@ -56,6 +56,13 @@ TEST_F(Rdata_OPT_Test, createFromWire) {
InvalidRdataLength);
}
+TEST_F(Rdata_OPT_Test, createFromLexer) {
+ // OPT RR cannot be created from text. Exceptions cause NULL to be
+ // returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::OPT(), RRClass::IN(),
+ "this does not matter"));
+}
+
TEST_F(Rdata_OPT_Test, toWireBuffer) {
rdata_opt.toWire(obuffer);
EXPECT_EQ(0, obuffer.getLength());
diff --git a/src/lib/dns/tests/rdata_ptr_unittest.cc b/src/lib/dns/tests/rdata_ptr_unittest.cc
index 86160fb..44b849a 100644
--- a/src/lib/dns/tests/rdata_ptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_ptr_unittest.cc
@@ -90,6 +90,12 @@ TEST_F(Rdata_PTR_Test, createFromWire) {
InvalidRdataLength);
}
+TEST_F(Rdata_PTR_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_ptr.compare(
+ *test::createRdataUsingLexer(RRType::PTR(), RRClass::IN(),
+ "ns.example.com")));
+}
+
TEST_F(Rdata_PTR_Test, toWireBuffer) {
rdata_ptr.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
index 20f32b9..5508d9c 100644
--- a/src/lib/dns/tests/rdata_rp_unittest.cc
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -106,6 +106,17 @@ TEST_F(Rdata_RP_Test, createFromParams) {
EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
}
+TEST_F(Rdata_RP_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_rp.compare(
+ *test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
+ "root.example.com. "
+ "rp-text.example.com.")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::RP(), RRClass::IN(),
+ "mailbox.example.com."));
+}
+
TEST_F(Rdata_RP_Test, toWireBuffer) {
// construct expected data
UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
diff --git a/src/lib/dns/tests/rdata_rrsig_unittest.cc b/src/lib/dns/tests/rdata_rrsig_unittest.cc
index 3324b99..d758ff3 100644
--- a/src/lib/dns/tests/rdata_rrsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_rrsig_unittest.cc
@@ -36,16 +36,21 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_RRSIG_Test : public RdataTest {
- // there's nothing to specialize
+public:
+ Rdata_RRSIG_Test() :
+ rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
+ "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
+ "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
+ "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
+ "f49t+sXKPzbipN9g+s1ZPiIyofc="),
+ rdata_rrsig(rrsig_txt)
+ {}
+
+ const string rrsig_txt;
+ const generic::RRSIG rdata_rrsig;
};
TEST_F(Rdata_RRSIG_Test, fromText) {
- string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
- "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
- "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
- "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc=");
- generic::RRSIG rdata_rrsig(rrsig_txt);
EXPECT_EQ(rrsig_txt, rdata_rrsig.toText());
EXPECT_EQ(isc::dns::RRType::A(), rdata_rrsig.typeCovered());
}
@@ -96,35 +101,37 @@ TEST_F(Rdata_RRSIG_Test, DISABLED_badText) {
"8496isc.org. ofc="), InvalidRdataText);
}
+TEST_F(Rdata_RRSIG_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_rrsig.compare(
+ *test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(),
+ rrsig_txt)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::RRSIG(), RRClass::IN(),
+ "INVALIDINPUT"));
+}
+
TEST_F(Rdata_RRSIG_Test, toWireRenderer) {
- string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
- "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
- "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
- "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc=");
- generic::RRSIG rdata_rrsig(rrsig_txt);
+ // FIXME: This doesn't check the result.
rdata_rrsig.toWire(renderer);
}
TEST_F(Rdata_RRSIG_Test, toWireBuffer) {
- string rrsig_txt("A 5 4 43200 20100223214617 20100222214617 8496 isc.org. "
- "evxhlGx13mpKLVkKsjpGzycS5twtIoxOmlN14w9t5AgzGBmz"
- "diGdLIrFabqr72af2rUq+UDBKMWXujwZTZUTws32sVldDPk/"
- "NbuacJM25fQXfv5mO3Af7TOoow3AjMaVG9icjCW0V55WcWQU"
- "f49t+sXKPzbipN9g+s1ZPiIyofc=");
- generic::RRSIG rdata_rrsig(rrsig_txt);
+ // FIXME: This doesn't check the result.
rdata_rrsig.toWire(obuffer);
}
TEST_F(Rdata_RRSIG_Test, createFromWire) {
- string rrsig_txt("A 5 2 43200 20100327070149 20100225070149 2658 isc.org. "
- "HkJk/xZTvzePU8NENl/ley8bbUumhk1hXciyqhLnz1VQFzkDooej6neX"
- "ZgWZzQKeTKPOYWrnYtdZW4PnPQFeUl3orgLev7F8J6FZlDn0y/J/ThR5"
- "m36Mo2/Gdxjj8lJ/IjPVkdpKyBpcnYND8KEIma5MyNCNeyO1UkfPQZGHNSQ=");
- EXPECT_EQ(rrsig_txt, rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
- "rdata_rrsig_fromWire1")->toText());
- generic::RRSIG rdata_rrsig(rrsig_txt);
- EXPECT_EQ(0, rdata_rrsig.compare(
+ const string rrsig_txt2(
+ "A 5 2 43200 20100327070149 20100225070149 2658 isc.org. "
+ "HkJk/xZTvzePU8NENl/ley8bbUumhk1hXciyqhLnz1VQFzkDooej6neX"
+ "ZgWZzQKeTKPOYWrnYtdZW4PnPQFeUl3orgLev7F8J6FZlDn0y/J/ThR5"
+ "m36Mo2/Gdxjj8lJ/IjPVkdpKyBpcnYND8KEIma5MyNCNeyO1UkfPQZGHNSQ=");
+ EXPECT_EQ(rrsig_txt2,
+ rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
+ "rdata_rrsig_fromWire1")->toText());
+ const generic::RRSIG rdata_rrsig2(rrsig_txt2);
+ EXPECT_EQ(0, rdata_rrsig2.compare(
*rdataFactoryFromFile(RRType("RRSIG"), RRClass("IN"),
"rdata_rrsig_fromWire1")));
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index a9d782c..ed1c29f 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -32,14 +32,130 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_SOA_Test : public RdataTest {
- // there's nothing to specialize
-};
+protected:
+ Rdata_SOA_Test() :
+ rdata_soa(Name("ns.example.com"),
+ Name("root.example.com"),
+ 2010012601, 3600, 300, 3600000, 1200)
+ {}
+
+ // Common check to see if the given text can be used to construct SOA
+ // Rdata that is identical rdata_soa.
+ void checkFromText(const char* soa_txt, const Name* origin = NULL) {
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ if (origin == NULL) {
+ // from-string constructor works correctly only when origin
+ // is NULL (by its nature).
+ EXPECT_EQ(0, generic::SOA(soa_txt).compare(rdata_soa));
+ }
+ EXPECT_EQ(0, generic::SOA(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb).compare(rdata_soa));
+ }
+
+ // Common check if given text (which is invalid as SOA RDATA) is rejected
+ // with the specified type of exception: ExForString is the expected
+ // exception for the "from string" constructor; ExForLexer is for the
+ // constructor with master lexer.
+ template <typename ExForString, typename ExForLexer>
+ void checkFromBadTexxt(const char* soa_txt, const Name* origin = NULL) {
+ EXPECT_THROW(generic::SOA soa(soa_txt), ExForString);
-const generic::SOA rdata_soa(Name("ns.example.com"), Name("root.example.com"),
- 2010012601, 3600, 300, 3600000, 1200);
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+ EXPECT_THROW(generic::SOA soa(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb), ExForLexer);
+ }
+
+ const generic::SOA rdata_soa;
+};
TEST_F(Rdata_SOA_Test, createFromText) {
- //TBD
+ // A simple case.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200");
+
+ // Beginning and trailing space are ignored.
+ checkFromText(" ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200 ");
+
+ // using extended TTL-like form for some parameters.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 1H 5M 1000H 20M");
+
+ // multi-line.
+ checkFromText("ns.example.com. (root.example.com.\n"
+ "2010012601 1H 5M 1000H) 20M");
+
+ // relative names for MNAME and RNAME with a separate origin (lexer
+ // version only)
+ const Name origin("example.com");
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M", &origin);
+
+ // with the '@' notation with a separate origin (lexer version only)
+ const Name full_mname("ns.example.com");
+ checkFromText("@ root.example.com. 2010012601 1H 5M 1000H 20M",
+ &full_mname);
+
+ // bad MNAME/RNAMEs
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ "bad..example. . 2010012601 1H 5M 1000H 20M");
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ ". bad..example. 2010012601 1H 5M 1000H 20M");
+
+ // Names shouldn't be quoted. (Note: on completion of #2534, the resulting
+ // exception will be different).
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ "\".\" . 0 0 0 0 0");
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ ". \".\" 0 0 0 0 0");
+
+ // Missing MAME or RNAME: for the string version, the serial would be
+ // tried as RNAME and result in "not absolute". For the lexer version,
+ // it reaches the end-of-line, missing min TTL.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". 2010012601 0 0 0 0", &Name::ROOT_NAME());
+
+ // bad serial. the string version converts lexer error to
+ // InvalidRdataText.
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . bad 0 0 0 0");
+
+ // bad serial; exceeding the uint32_t range (4294967296 = 2^32)
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . 4294967296 0 0 0 0");
+
+ // Bad format for other numeric parameters. These will be tried as a TTL,
+ // and result in an exception there.
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 bad 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 4294967296 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 bad 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 4294967296 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 bad 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 4294967296 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 0 bad");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 0 4294967296");
+
+ // No space between RNAME and serial. This case is the same as missing
+ // M/RNAME.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". example.0 0 0 0 0", &Name::ROOT_NAME());
+
+ // Extra parameter. string version immediately detects the error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0 extra"), InvalidRdataText);
+ // Likewise. Redundant newline is also considered an error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0\n"), InvalidRdataText);
+ EXPECT_THROW(generic::SOA soa("\n. . 0 0 0 0 0"), InvalidRdataText);
+ // lexer version defers the check to the upper layer (we pass origin
+ // to skip the check with the string version).
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M extra", &origin);
}
TEST_F(Rdata_SOA_Test, createFromWire) {
@@ -49,6 +165,13 @@ TEST_F(Rdata_SOA_Test, createFromWire) {
// TBD: more tests
}
+TEST_F(Rdata_SOA_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_soa.compare(
+ *test::createRdataUsingLexer(RRType::SOA(), RRClass::IN(),
+ "ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200")));
+}
+
TEST_F(Rdata_SOA_Test, toWireRenderer) {
renderer.skip(2);
rdata_soa.toWire(renderer);
@@ -79,4 +202,51 @@ TEST_F(Rdata_SOA_Test, getSerial) {
EXPECT_EQ(2010012601, rdata_soa.getSerial().getValue());
}
+TEST_F(Rdata_SOA_Test, getMinimum) {
+ EXPECT_EQ(1200, rdata_soa.getMinimum());
+
+ // Also check with a very large number (with the MSB being 1).
+ EXPECT_EQ(2154848336u, generic::SOA(Name("ns.example.com"),
+ Name("root.example.com"),
+ 0, 0, 0, 0, 0x80706050).getMinimum());
+}
+
+void
+compareCheck(const generic::SOA& small, const generic::SOA& large) {
+ EXPECT_GT(0, small.compare(large));
+ EXPECT_LT(0, large.compare(small));
+}
+
+TEST_F(Rdata_SOA_Test, compare) {
+ // Check simple equivalence
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200")));
+ // Check name comparison is case insensitive
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "NS.example.com. root.EXAMPLE.com. "
+ "2010012601 3600 300 3600000 1200")));
+
+ // Check names are compared in the RDATA comparison semantics (different
+ // from DNSSEC ordering for owner names)
+ compareCheck(generic::SOA("a.example. . 0 0 0 0 0"),
+ generic::SOA("example. . 0 0 0 0 0"));
+ compareCheck(generic::SOA(". a.example. 0 0 0 0 0"),
+ generic::SOA(". example. 0 0 0 0 0"));
+
+ // Compare other numeric fields: 1076895760 = 0x40302010,
+ // 270544960 = 0x10203040. These are chosen to make sure that machine
+ // endian doesn't confuse the comparison results.
+ compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
+ generic::SOA(". . 1076895760 0 0 0 0"));
+ compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
+ generic::SOA(". . 0 1076895760 0 0 0"));
+ compareCheck(generic::SOA(". . 0 0 270544960 0 0"),
+ generic::SOA(". . 0 0 1076895760 0 0"));
+ compareCheck(generic::SOA(". . 0 0 0 270544960 0"),
+ generic::SOA(". . 0 0 0 1076895760 0"));
+ compareCheck(generic::SOA(". . 0 0 0 0 270544960"),
+ generic::SOA(". . 0 0 0 0 1076895760"));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_srv_unittest.cc b/src/lib/dns/tests/rdata_srv_unittest.cc
index b194b1c..066755f 100644
--- a/src/lib/dns/tests/rdata_srv_unittest.cc
+++ b/src/lib/dns/tests/rdata_srv_unittest.cc
@@ -119,6 +119,17 @@ TEST_F(Rdata_SRV_Test, createFromWire) {
"rdata_srv_fromWire", 89)));
}
+TEST_F(Rdata_SRV_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_srv.compare(
+ *test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 1500 a.example.com.")));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::SRV(), RRClass::IN(),
+ "1 5 281474976710656 "
+ "a.example.com."));
+}
+
TEST_F(Rdata_SRV_Test, toWireBuffer) {
rdata_srv.toWire(obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
diff --git a/src/lib/dns/tests/rdata_sshfp_unittest.cc b/src/lib/dns/tests/rdata_sshfp_unittest.cc
index dccd8b1..6c13ad9 100644
--- a/src/lib/dns/tests/rdata_sshfp_unittest.cc
+++ b/src/lib/dns/tests/rdata_sshfp_unittest.cc
@@ -68,6 +68,12 @@ TEST_F(Rdata_SSHFP_Test, createFromText) {
EXPECT_EQ(0, rdata_sshfp4.compare(rdata_sshfp));
}
+TEST_F(Rdata_SSHFP_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_sshfp.compare(
+ *test::createRdataUsingLexer(RRType::SSHFP(), RRClass::IN(),
+ "2 1 123456789abcdef67890123456789abcdef67890")));
+}
+
TEST_F(Rdata_SSHFP_Test, algorithmTypes) {
// Some of these may not be RFC conformant, but we relax the check
// in our code to work with algorithm and fingerprint types that may
diff --git a/src/lib/dns/tests/rdata_tsig_unittest.cc b/src/lib/dns/tests/rdata_tsig_unittest.cc
index c8ee8ac..df35842 100644
--- a/src/lib/dns/tests/rdata_tsig_unittest.cc
+++ b/src/lib/dns/tests/rdata_tsig_unittest.cc
@@ -247,6 +247,16 @@ TEST_F(Rdata_TSIG_Test, createFromParams) {
isc::InvalidParameter);
}
+TEST_F(Rdata_TSIG_Test, createFromLexer) {
+ EXPECT_EQ(0, rdata_tsig.compare(
+ *test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
+ valid_text1)));
+
+ // Exceptions cause NULL to be returned.
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::TSIG(), RRClass::ANY(),
+ "foo 0 0 0 0 BADKEY 0 0"));
+}
+
TEST_F(Rdata_TSIG_Test, assignment) {
any::TSIG copy((string(valid_text2)));
copy = rdata_tsig;
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index 981265e..cb3c44d 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -17,17 +17,25 @@
#include <util/buffer.h>
#include <dns/exceptions.h>
#include <dns/rdataclass.h>
-#include <gtest/gtest.h>
#include <dns/tests/unittest_util.h>
#include <dns/tests/rdata_unittest.h>
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
+#include <sstream>
+#include <vector>
+
using isc::UnitTestUtil;
using namespace std;
using namespace isc::dns;
using namespace isc::util;
using namespace isc::dns::rdata;
+namespace {
template<class T>
class RRTYPE : public RRType {
@@ -38,74 +46,187 @@ public:
template<> RRTYPE<generic::TXT>::RRTYPE() : RRType(RRType::TXT()) {}
template<> RRTYPE<generic::SPF>::RRTYPE() : RRType(RRType::SPF()) {}
-namespace {
const uint8_t wiredata_txt_like[] = {
- sizeof("Test String") - 1,
- 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+ sizeof("Test-String") - 1,
+ 'T', 'e', 's', 't', '-', 'S', 't', 'r', 'i', 'n', 'g'
};
const uint8_t wiredata_nulltxt[] = { 0 };
-vector<uint8_t> wiredata_longesttxt(256, 'a');
template<class TXT_LIKE>
class Rdata_TXT_LIKE_Test : public RdataTest {
protected:
- Rdata_TXT_LIKE_Test() {
+ Rdata_TXT_LIKE_Test() :
+ wiredata_longesttxt(256, 'a'),
+ rdata_txt_like("Test-String"),
+ rdata_txt_like_empty("\"\""),
+ rdata_txt_like_quoted("\"Test-String\"")
+ {
wiredata_longesttxt[0] = 255; // adjust length
}
- static const TXT_LIKE rdata_txt_like;
- static const TXT_LIKE rdata_txt_like_empty;
- static const TXT_LIKE rdata_txt_like_quoted;
+protected:
+ vector<uint8_t> wiredata_longesttxt;
+ const TXT_LIKE rdata_txt_like;
+ const TXT_LIKE rdata_txt_like_empty;
+ const TXT_LIKE rdata_txt_like_quoted;
};
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like("Test String");
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_empty("");
-
-template<class TXT_LIKE>
-const TXT_LIKE Rdata_TXT_LIKE_Test<TXT_LIKE>::rdata_txt_like_quoted
- ("\"Test String\"");
-
// The list of types we want to test.
typedef testing::Types<generic::TXT, generic::SPF> Implementations;
TYPED_TEST_CASE(Rdata_TXT_LIKE_Test, Implementations);
TYPED_TEST(Rdata_TXT_LIKE_Test, createFromText) {
- // normal case is covered in toWireBuffer.
+ // Below we check the behavior for the "from text" constructors, both
+ // from std::string and with MasterLexer. The underlying implementation
+ // is the same, so both should work exactly same, but we confirm both
+ // cases.
+
+ const std::string multi_line = "(\n \"Test-String\" )";
+ const std::string escaped_txt = "Test\\045Strin\\g";
+
+ // test input for the lexer version
+ std::stringstream ss;
+ ss << "Test-String\n";
+ ss << "\"Test-String\"\n"; // explicitly surrounded by '"'s
+ ss << multi_line << "\n"; // multi-line text with ()
+ ss << escaped_txt << "\n"; // using the two types of escape with '\'
+ ss << "\"\"\n"; // empty string (note: still valid char-str)
+ ss << string(255, 'a') << "\n"; // Longest possible character-string.
+ ss << string(256, 'a') << "\n"; // char-string too long
+ ss << "\"Test-String\\\"\n"; // unbalanced quote
+ ss << "\"Test-String\\\"\"\n";
+ this->lexer.pushSource(ss);
+
+ // commonly used Rdata to compare below, created from wire
+ ConstRdataPtr const rdata =
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"), "rdata_txt_fromWire1");
+
+ // normal case is covered in toWireBuffer. First check the std::string
+ // case, then with MasterLexer. For the latter, we need to read and skip
+ // '\n'. These apply to most of the other cases below.
+ EXPECT_EQ(0, this->rdata_txt_like.compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
// surrounding double-quotes shouldn't change the result.
- EXPECT_EQ(0, this->rdata_txt_like.compare(this->rdata_txt_like_quoted));
+ EXPECT_EQ(0, this->rdata_txt_like_quoted.compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // multi-line input with ()
+ EXPECT_EQ(0, TypeParam(multi_line).compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+
+ // for the same data using escape
+ EXPECT_EQ(0, TypeParam(escaped_txt).compare(*rdata));
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
// Null character-string.
this->obuffer.clear();
- TypeParam(string("")).toWire(this->obuffer);
+ TypeParam(string("\"\"")).toWire(this->obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- this->obuffer.getData(),
- this->obuffer.getLength(),
+ this->obuffer.getData(), this->obuffer.getLength(),
wiredata_nulltxt, sizeof(wiredata_nulltxt));
+ this->obuffer.clear();
+ TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+ toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(), this->obuffer.getLength(),
+ wiredata_nulltxt, sizeof(wiredata_nulltxt));
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
// Longest possible character-string.
this->obuffer.clear();
TypeParam(string(255, 'a')).toWire(this->obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
- this->obuffer.getData(),
- this->obuffer.getLength(),
- &wiredata_longesttxt[0], wiredata_longesttxt.size());
+ this->obuffer.getData(), this->obuffer.getLength(),
+ &this->wiredata_longesttxt[0],
+ this->wiredata_longesttxt.size());
+ this->obuffer.clear();
+ TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS, this->loader_cb).
+ toWire(this->obuffer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ this->obuffer.getData(), this->obuffer.getLength(),
+ &this->wiredata_longesttxt[0],
+ this->wiredata_longesttxt.size());
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
// Too long text for a valid character-string.
EXPECT_THROW(TypeParam(string(256, 'a')), CharStringTooLong);
+ EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb), CharStringTooLong);
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
// The escape character makes the double quote a part of character-string,
// so this is invalid input and should be rejected.
- EXPECT_THROW(TypeParam("\"Test String\\\""), InvalidRdataText);
+ EXPECT_THROW(TypeParam("\"Test-String\\\""), InvalidRdataText);
+ EXPECT_THROW(TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb), MasterLexer::LexerError);
+ EXPECT_EQ(MasterToken::END_OF_LINE, this->lexer.getNextToken().getType());
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createMultiStringsFromText) {
+ // Tests for "from text" variants construction with various forms of
+ // multi character-strings.
+
+ std::vector<std::string > texts;
+ texts.push_back("\"Test-String\" \"Test-String\""); // most common form
+ texts.push_back("\"Test-String\"\"Test-String\""); // no space between'em
+ texts.push_back("\"Test-String\" Test-String"); // no '"' for one
+ texts.push_back("\"Test-String\"Test-String"); // and no space either
+ texts.push_back("Test-String \"Test-String\""); // no '"' for the other
+ // This one currently doesn't work
+ //texts.push_back("Test-String\"Test-String\""); // and no space either
+
+ std::stringstream ss;
+ for (std::vector<std::string >::const_iterator it = texts.begin();
+ it != texts.end(); ++it) {
+ ss << *it << "\n";
+ }
+ this->lexer.pushSource(ss);
+
+ // The corresponding Rdata built from wire to compare in the checks below.
+ ConstRdataPtr const rdata =
+ this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"), "rdata_txt_fromWire3.wire");
+
+ // Confirm we can construct the Rdata from the test text, both from
+ // std::string and with lexer, and that matches the from-wire data.
+ for (std::vector<std::string >::const_iterator it = texts.begin();
+ it != texts.end(); ++it) {
+ SCOPED_TRACE(*it);
+ EXPECT_EQ(0, TypeParam(*it).compare(*rdata));
+
+ EXPECT_EQ(0, TypeParam(this->lexer, NULL, MasterLoader::MANY_ERRORS,
+ this->loader_cb).compare(*rdata));
+ EXPECT_EQ(MasterToken::END_OF_LINE,
+ this->lexer.getNextToken().getType());
+ }
+}
+
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromTextExtra) {
+ // This is for the std::string version only: the input must end with EOF;
+ // an extra new-line will result in an exception.
+ EXPECT_THROW(TypeParam("\"Test-String\"\n"), InvalidRdataText);
+ // Same if there's a space before '\n'
+ EXPECT_THROW(TypeParam("\"Test-String\" \n"), InvalidRdataText);
+}
- // Terminating double-quote is provided, so this is valid, but in this
- // version of implementation we reject escaped characters.
- EXPECT_THROW(TypeParam("\"Test String\\\"\""), InvalidRdataText);
+TYPED_TEST(Rdata_TXT_LIKE_Test, fromTextEmpty) {
+ // If the input text doesn't contain any character-string, it should be
+ // rejected
+ EXPECT_THROW(TypeParam(""), InvalidRdataText);
+ EXPECT_THROW(TypeParam(" "), InvalidRdataText); // even with a space
+ EXPECT_THROW(TypeParam("(\n)"), InvalidRdataText); // or multi-line with ()
}
void
@@ -129,13 +250,15 @@ makeLargest(vector<uint8_t>& data) {
TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
EXPECT_EQ(0, this->rdata_txt_like.compare(
- *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
- "rdata_txt_fromWire1")));
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"),
+ "rdata_txt_fromWire1")));
// Empty character string
EXPECT_EQ(0, this->rdata_txt_like_empty.compare(
- *this->rdataFactoryFromFile(RRTYPE<TypeParam>(), RRClass("IN"),
- "rdata_txt_fromWire2.wire")));
+ *this->rdataFactoryFromFile(RRTYPE<TypeParam>(),
+ RRClass("IN"),
+ "rdata_txt_fromWire2.wire")));
// Multiple character strings
this->obuffer.clear();
@@ -185,6 +308,12 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, createFromWire) {
DNSMessageFORMERR);
}
+TYPED_TEST(Rdata_TXT_LIKE_Test, createFromLexer) {
+ EXPECT_EQ(0, this->rdata_txt_like.compare(
+ *test::createRdataUsingLexer(RRTYPE<TypeParam>(), RRClass::IN(),
+ "Test-String")));
+}
+
TYPED_TEST(Rdata_TXT_LIKE_Test, toWireBuffer) {
this->rdata_txt_like.toWire(this->obuffer);
EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
@@ -202,7 +331,25 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
}
TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
- EXPECT_EQ("\"Test String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+ // Check escape behavior
+ const TypeParam double_quotes("Test-String\"Test-String\"");
+ EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+ const TypeParam semicolon("Test-String\\;Test-String");
+ EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+ const TypeParam backslash("Test-String\\\\Test-String");
+ EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+ const TypeParam before_x20("Test-String\\031Test-String");
+ EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+ const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+ const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+ const TypeParam after_x7e("Test-String\\127Test-String");
+ EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
}
TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
@@ -232,8 +379,8 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, compare) {
EXPECT_EQ(TypeParam(txt1).compare(TypeParam(txt1)), 0);
- EXPECT_LT(TypeParam("").compare(TypeParam(txt1)), 0);
- EXPECT_GT(TypeParam(txt1).compare(TypeParam("")), 0);
+ EXPECT_LT(TypeParam("\"\"").compare(TypeParam(txt1)), 0);
+ EXPECT_GT(TypeParam(txt1).compare(TypeParam("\"\"")), 0);
EXPECT_LT(TypeParam(txt1).compare(TypeParam(txt2)), 0);
EXPECT_GT(TypeParam(txt2).compare(TypeParam(txt1)), 0);
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index bf1f5f7..67eae2f 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -28,6 +28,9 @@
#include <dns/tests/unittest_util.h>
#include <dns/tests/rdata_unittest.h>
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
using isc::UnitTestUtil;
using namespace std;
using namespace isc::dns;
@@ -38,7 +41,8 @@ namespace isc {
namespace dns {
namespace rdata {
RdataTest::RdataTest() :
- obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+ loader_cb(MasterLoaderCallbacks::getNullCallbacks())
{}
RdataPtr
@@ -54,6 +58,159 @@ RdataTest::rdataFactoryFromFile(const RRType& rrtype, const RRClass& rrclass,
uint16_t rdlen = buffer.readUint16();
return (createRdata(rrtype, rrclass, buffer, rdlen));
}
+
+namespace test {
+
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& str)
+{
+ std::stringstream ss(str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ MasterLoaderCallbacks callbacks =
+ MasterLoaderCallbacks::getNullCallbacks();
+ const Name origin("example.org.");
+
+ return (createRdata(rrtype, rrclass, lexer, &origin,
+ MasterLoader::MANY_ERRORS, callbacks));
+}
+
+} // end of namespace isc::dns::rdata::test
+
+// A mock class to check parameters passed via loader callbacks. Its callback
+// records the passed parameters, allowing the test to check them later via
+// the check() method.
+class CreateRdataCallback {
+public:
+ enum CallbackType { NONE, ERROR, WARN };
+ CreateRdataCallback() : type_(NONE), line_(0) {}
+ void callback(CallbackType type, const string& source, size_t line,
+ const string& reason_txt) {
+ type_ = type;
+ source_ = source;
+ line_ = line;
+ reason_txt_ = reason_txt;
+ }
+
+ void clear() {
+ type_ = NONE;
+ source_.clear();
+ line_ = 0;
+ reason_txt_.clear();
+ }
+
+ // Return if callback is called since the previous call to clear().
+ bool isCalled() const { return (type_ != NONE); }
+
+ void check(const string& expected_srcname, size_t expected_line,
+ CallbackType expected_type, const string& expected_reason)
+ const
+ {
+ EXPECT_EQ(expected_srcname, source_);
+ EXPECT_EQ(expected_line, line_);
+ EXPECT_EQ(expected_type, type_);
+ EXPECT_EQ(expected_reason, reason_txt_);
+ }
+
+private:
+ CallbackType type_;
+ string source_;
+ size_t line_;
+ string reason_txt_;
+};
+
+// Test class/type-independent behavior of createRdata().
+TEST_F(RdataTest, createRdataWithLexer) {
+ const in::AAAA aaaa_rdata("2001:db8::1");
+
+ stringstream ss;
+ const string src_name = "stream-" + boost::lexical_cast<string>(&ss);
+ ss << aaaa_rdata.toText() << "\n"; // valid case
+ ss << aaaa_rdata.toText() << "; comment, should be ignored\n";
+ ss << aaaa_rdata.toText() << " extra-token\n"; // extra token
+ ss << aaaa_rdata.toText() << " extra token\n"; // 2 extra tokens
+ ss << ")\n"; // causing lexer error in parsing the RDATA text
+ ss << "192.0.2.1\n"; // semantics error: IPv4 address is given for AAAA
+ ss << aaaa_rdata.toText(); // valid, but end with EOF, not EOL
+ lexer.pushSource(ss);
+
+ CreateRdataCallback callback;
+ MasterLoaderCallbacks callbacks(
+ boost::bind(&CreateRdataCallback::callback, &callback,
+ CreateRdataCallback::ERROR, _1, _2, _3),
+ boost::bind(&CreateRdataCallback::callback, &callback,
+ CreateRdataCallback::WARN, _1, _2, _3));
+
+ size_t line = 0;
+
+ // Valid case.
+ ++line;
+ ConstRdataPtr rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer,
+ NULL, MasterLoader::MANY_ERRORS,
+ callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ EXPECT_FALSE(callback.isCalled());
+
+ // Similar to the previous case, but RDATA is followed by a comment.
+ // It should cause any confusion.
+ ++line;
+ callback.clear();
+ rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ EXPECT_FALSE(callback.isCalled());
+
+ // Broken RDATA text: extra token. createRdata() returns NULL, error
+ // callback is called.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed near 'extra-token': "
+ "extra input text");
+
+ // Similar to the previous case, but only the first extra token triggers
+ // callback.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed near 'extra': "
+ "extra input text");
+
+ // Lexer error will happen, corresponding error callback will be triggered.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed: unbalanced parentheses");
+
+ // Semantics level error will happen, corresponding error callback will be
+ // triggered.
+ ++line;
+ callback.clear();
+ EXPECT_FALSE(createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks));
+ callback.check(src_name, line, CreateRdataCallback::ERROR,
+ "createRdata from text failed: Failed to convert "
+ "'192.0.2.1' to IN/AAAA RDATA");
+
+ // Input is valid and parse will succeed, but with a warning that the
+ // file is not ended with a newline.
+ ++line;
+ callback.clear();
+ rdata = createRdata(RRType::AAAA(), RRClass::IN(), lexer, NULL,
+ MasterLoader::MANY_ERRORS, callbacks);
+ EXPECT_EQ(0, aaaa_rdata.compare(*rdata));
+ callback.check(src_name, line, CreateRdataCallback::WARN,
+ "file does not end with newline");
+}
+
}
}
}
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index f593609..3f1dbb3 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -20,6 +20,7 @@
#include <dns/rrclass.h>
#include <dns/rrtype.h>
#include <dns/rdata.h>
+#include <dns/master_lexer.h>
#include <gtest/gtest.h>
@@ -40,12 +41,21 @@ protected:
/// This is an RDATA object of some "unknown" RR type so that it can be
/// used to test the compare() method against a well-known RR type.
RdataPtr rdata_nomatch;
+ MasterLexer lexer;
+ MasterLoaderCallbacks loader_cb;
};
+
+namespace test {
+RdataPtr
+createRdataUsingLexer(const RRType& rrtype, const RRClass& rrclass,
+ const std::string& str);
+}
+
}
}
}
#endif // RDATA_UNITTEST_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 6156be5..11f1c54 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -59,7 +59,7 @@ RRClassTest::rrclassFactoryFromWire(const char* datafile) {
return (RRClass(buffer));
}
-TEST_F(RRClassTest, fromText) {
+TEST_F(RRClassTest, fromTextConstructor) {
EXPECT_EQ("IN", RRClass("IN").toText());
EXPECT_EQ("CH", RRClass("CH").toText());
@@ -96,6 +96,14 @@ TEST_F(RRClassTest, toText) {
EXPECT_EQ("CLASS65000", RRClass(65000).toText());
}
+TEST_F(RRClassTest, createFromText) {
+ const MaybeRRClass rrclass("IN");
+ EXPECT_TRUE(rrclass);
+ EXPECT_EQ("IN", rrclass->toText());
+ EXPECT_TRUE(RRClass::createFromText("CH"));
+ EXPECT_FALSE(RRClass::createFromText("ZZ"));
+}
+
TEST_F(RRClassTest, toWireBuffer) {
rrclass_1.toWire(obuffer);
rrclass_0x80.toWire(obuffer);
diff --git a/src/lib/dns/tests/rrcollator_unittest.cc b/src/lib/dns/tests/rrcollator_unittest.cc
new file mode 100644
index 0000000..e66f87c
--- /dev/null
+++ b/src/lib/dns/tests/rrcollator_unittest.cc
@@ -0,0 +1,214 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/rrclass.h>
+#include <dns/rrcollator.h>
+#include <dns/rdata.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+
+#include <sstream>
+#include <vector>
+
+using std::vector;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+typedef RRCollator::AddRRsetCallback AddRRsetCallback;
+
+void
+addRRset(const RRsetPtr& rrset, vector<ConstRRsetPtr>* to_append,
+ const bool* do_throw) {
+ if (*do_throw) {
+ isc_throw(isc::Unexpected, "faked failure");
+ }
+ to_append->push_back(rrset);
+}
+
+class RRCollatorTest : public ::testing::Test {
+protected:
+ RRCollatorTest() :
+ origin_("example.com"), rrclass_(RRClass::IN()), rrttl_(3600),
+ throw_from_callback_(false),
+ collator_(boost::bind(addRRset, _1, &rrsets_, &throw_from_callback_)),
+ rr_callback_(collator_.getCallback()),
+ a_rdata1_(createRdata(RRType::A(), rrclass_, "192.0.2.1")),
+ a_rdata2_(createRdata(RRType::A(), rrclass_, "192.0.2.2")),
+ txt_rdata_(createRdata(RRType::TXT(), rrclass_, "test")),
+ sig_rdata1_(createRdata(RRType::RRSIG(), rrclass_,
+ "A 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKE\n")),
+ sig_rdata2_(createRdata(RRType::RRSIG(), rrclass_,
+ "NS 5 3 3600 20000101000000 20000201000000 "
+ "12345 example.com. FAKE\n"))
+ {}
+
+ void checkRRset(const Name& expected_name, const RRClass& expected_class,
+ const RRType& expected_type, const RRTTL& expected_ttl,
+ const vector<ConstRdataPtr>& expected_rdatas) {
+ SCOPED_TRACE(expected_name.toText(true) + "/" +
+ expected_class.toText() + "/" + expected_type.toText());
+
+ // This test always clears rrsets_ to confirm RRsets are added
+ // one-by-one
+ ASSERT_EQ(1, rrsets_.size());
+
+ ConstRRsetPtr actual = rrsets_[0];
+ EXPECT_EQ(expected_name, actual->getName());
+ EXPECT_EQ(expected_class, actual->getClass());
+ EXPECT_EQ(expected_type, actual->getType());
+ EXPECT_EQ(expected_ttl, actual->getTTL());
+ ASSERT_EQ(expected_rdatas.size(), actual->getRdataCount());
+ vector<ConstRdataPtr>::const_iterator it = expected_rdatas.begin();
+ for (RdataIteratorPtr rit = actual->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ EXPECT_EQ(0, rit->getCurrent().compare(**it));
+ ++it;
+ }
+
+ rrsets_.clear();
+ }
+
+ const Name origin_;
+ const RRClass rrclass_;
+ const RRTTL rrttl_;
+ vector<ConstRRsetPtr> rrsets_;
+ bool throw_from_callback_;
+ RRCollator collator_;
+ AddRRCallback rr_callback_;
+ const RdataPtr a_rdata1_, a_rdata2_, txt_rdata_, sig_rdata1_, sig_rdata2_;
+ vector<ConstRdataPtr> rdatas_; // placeholder for expected data
+};
+
+TEST_F(RRCollatorTest, basicCases) {
+ // Add two RRs belonging to the same RRset. These will be buffered.
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);
+ EXPECT_TRUE(rrsets_.empty()); // not yet given as an RRset
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata2_);
+ EXPECT_TRUE(rrsets_.empty()); // still not given
+
+ // Add another type of RR. This completes the construction of the A RRset,
+ // which will be given via the callback.
+ rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_, txt_rdata_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+
+ // Add the same type of RR but of different name. This should make another
+ // callback for the previous TXT RR.
+ rr_callback_(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
+ txt_rdata_);
+ rdatas_.clear();
+ rdatas_.push_back(txt_rdata_);
+ checkRRset(origin_, rrclass_, RRType::TXT(), rrttl_, rdatas_);
+
+ // Add the same type and name of RR but of different class (rare case
+ // in practice)
+ rr_callback_(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
+ txt_rdata_);
+ rdatas_.clear();
+ rdatas_.push_back(txt_rdata_);
+ checkRRset(Name("txt.example.com"), rrclass_, RRType::TXT(), rrttl_,
+ rdatas_);
+
+ // Tell the collator we are done, then we'll see the last RR as an RRset.
+ collator_.flush();
+ checkRRset(Name("txt.example.com"), RRClass::CH(), RRType::TXT(), rrttl_,
+ rdatas_);
+
+ // Redundant flush() will be no-op.
+ collator_.flush();
+ EXPECT_TRUE(rrsets_.empty());
+}
+
+TEST_F(RRCollatorTest, minTTLFirst) {
+ // RRs of the same RRset but has different TTLs. The first RR has
+ // the smaller TTL, which should be used for the TTL of the RRset.
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata2_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ collator_.flush();
+ checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
+}
+
+TEST_F(RRCollatorTest, maxTTLFirst) {
+ // RRs of the same RRset but has different TTLs. The second RR has
+ // the smaller TTL, which should be used for the TTL of the RRset.
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(20), a_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::A(), RRTTL(10), a_rdata2_);
+ rdatas_.push_back(a_rdata1_);
+ rdatas_.push_back(a_rdata2_);
+ collator_.flush();
+ checkRRset(origin_, rrclass_, RRType::A(), RRTTL(10), rdatas_);
+}
+
+TEST_F(RRCollatorTest, addRRSIGs) {
+ // RRSIG is special; they are also distinguished by their covered types.
+ rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata1_);
+ rr_callback_(origin_, rrclass_, RRType::RRSIG(), rrttl_, sig_rdata2_);
+
+ rdatas_.push_back(sig_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::RRSIG(), rrttl_, rdatas_);
+}
+
+TEST_F(RRCollatorTest, emptyFlush) {
+ collator_.flush();
+ EXPECT_TRUE(rrsets_.empty());
+}
+
+TEST_F(RRCollatorTest, throwFromCallback) {
+ // Adding an A RR
+ rr_callback_(origin_, rrclass_, RRType::A(), rrttl_, a_rdata1_);
+
+ // Adding a TXT RR, which would trigger RRset callback, but in this test
+ // it throws. The added TXT RR will be effectively lost.
+ throw_from_callback_ = true;
+ EXPECT_THROW(rr_callback_(origin_, rrclass_, RRType::TXT(), rrttl_,
+ txt_rdata_), isc::Unexpected);
+
+ // We'll only see the A RR.
+ throw_from_callback_ = false;
+ collator_.flush();
+ rdatas_.push_back(a_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+}
+
+TEST_F(RRCollatorTest, withMasterLoader) {
+ // Test a simple case with MasterLoader. There shouldn't be anything
+ // special, but that's the mainly intended usage of the collator, so we
+ // check it explicitly.
+ std::istringstream ss("example.com. 3600 IN A 192.0.2.1\n");
+ MasterLoader loader(ss, origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator_.getCallback());
+ loader.load();
+ collator_.flush();
+ rdatas_.push_back(a_rdata1_);
+ checkRRset(origin_, rrclass_, RRType::A(), rrttl_, rdatas_);
+}
+
+}
diff --git a/src/lib/dns/tests/rrparamregistry_unittest.cc b/src/lib/dns/tests/rrparamregistry_unittest.cc
index c155b53..08e0af1 100644
--- a/src/lib/dns/tests/rrparamregistry_unittest.cc
+++ b/src/lib/dns/tests/rrparamregistry_unittest.cc
@@ -24,6 +24,10 @@
#include <dns/rdataclass.h>
#include <dns/rrparamregistry.h>
#include <dns/rrtype.h>
+#include <dns/master_loader.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
using namespace std;
using namespace isc::dns;
@@ -104,6 +108,7 @@ TEST_F(RRParamRegistryTest, addError) {
class TestRdataFactory : public AbstractRdataFactory {
public:
+ using AbstractRdataFactory::create;
virtual RdataPtr create(const string& rdata_str) const
{ return (RdataPtr(new in::A(rdata_str))); }
virtual RdataPtr create(InputBuffer& buffer, size_t rdata_len) const
@@ -152,4 +157,33 @@ TEST_F(RRParamRegistryTest, addRemoveFactory) {
RRType(test_type_code)));
}
+RdataPtr
+createRdataHelper(const std::string& str) {
+ boost::scoped_ptr<AbstractRdataFactory> rdf(new TestRdataFactory);
+
+ std::stringstream ss(str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ MasterLoaderCallbacks callbacks(MasterLoaderCallbacks::getNullCallbacks());
+ const Name origin("example.org.");
+
+ return (rdf->create(lexer, &origin,
+ MasterLoader::MANY_ERRORS,
+ callbacks));
+}
+
+TEST_F(RRParamRegistryTest, createFromLexer) {
+ // This test basically checks that the string version of
+ // AbstractRdataFactory::create() is called by the MasterLexer
+ // variant of create().
+ EXPECT_EQ(0, in::A("192.168.0.1").compare(
+ *createRdataHelper("192.168.0.1")));
+
+ // This should parse only up to the end of line. Everything that
+ // comes afterwards is not parsed.
+ EXPECT_EQ(0, in::A("192.168.0.42").compare(
+ *createRdataHelper("192.168.0.42\na b c d e f")));
+}
+
}
diff --git a/src/lib/dns/tests/rrset_collection_unittest.cc b/src/lib/dns/tests/rrset_collection_unittest.cc
new file mode 100644
index 0000000..e17e9e7
--- /dev/null
+++ b/src/lib/dns/tests/rrset_collection_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <fstream>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+
+namespace {
+
+class RRsetCollectionTest : public ::testing::Test {
+public:
+ RRsetCollectionTest() :
+ rrclass("IN"),
+ origin("example.org"),
+ collection(TEST_DATA_SRCDIR "/example.org", origin, rrclass)
+ {}
+
+ const RRClass rrclass;
+ const Name origin;
+ RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, istreamConstructor) {
+ std::ifstream fs(TEST_DATA_SRCDIR "/example.org");
+ RRsetCollection collection2(fs, origin, rrclass);
+
+ RRsetCollectionBase::Iterator iter = collection.begin();
+ RRsetCollectionBase::Iterator iter2 = collection2.begin();
+ while (iter != collection.end()) {
+ ASSERT_TRUE(iter2 != collection2.end());
+ EXPECT_EQ((*iter).toText(), (*iter2).toText());
+ ++iter;
+ ++iter2;
+ }
+ ASSERT_TRUE(iter2 == collection2.end());
+}
+
+template <typename T, typename TP>
+void doFind(T& collection, const RRClass& rrclass) {
+ // Test the find() that returns ConstRRsetPtr
+ TP rrset = collection.find(Name("www.example.org"), rrclass, RRType::A());
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = collection.find(Name("foo.example.org"), rrclass, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = collection.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, findConst) {
+ // Test the find() that returns ConstRRsetPtr
+ const RRsetCollection& ccln = collection;
+ doFind<const RRsetCollection, ConstRRsetPtr>(ccln, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, find) {
+ // Test the find() that returns RRsetPtr
+ doFind<RRsetCollection, RRsetPtr>(collection, rrclass);
+}
+
+void
+doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
+ // foo.example.org/A doesn't exist
+ RRsetPtr rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Add foo.example.org/A
+ RRsetPtr rrset(new BasicRRset(Name("foo.example.org"), rrclass, RRType::A(),
+ RRTTL(7200)));
+ rrset->addRdata(in::A("192.0.2.1"));
+ collection.addRRset(rrset);
+
+ // foo.example.org/A should now exist
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_TRUE(rrset_found);
+ EXPECT_EQ(RRType::A(), rrset_found->getType());
+ EXPECT_EQ(RRTTL(7200), rrset_found->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset_found->getClass());
+ EXPECT_EQ(Name("foo.example.org"), rrset_found->getName());
+
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Adding a duplicate RRset must throw.
+ EXPECT_THROW({
+ collection.addRRset(rrset);
+ }, isc::InvalidParameter);
+
+ // Remove foo.example.org/A, which should pass
+ EXPECT_TRUE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+ // foo.example.org/A should not exist now
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Removing foo.example.org/A should fail now
+ EXPECT_FALSE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+}
+
+TEST_F(RRsetCollectionTest, addAndRemove) {
+ doAddAndRemove(collection, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, empty) {
+ RRsetCollection cln;
+
+ // Here, cln is empty.
+ EXPECT_TRUE(cln.end() == cln.begin());
+
+ doAddAndRemove(cln, rrclass);
+
+ // cln should be empty again here, after the add and remove
+ // operations.
+ EXPECT_TRUE(cln.end() == cln.begin());
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Here, we just count the records and do some basic tests on them.
+ size_t count = 0;
+ for (RRsetCollection::Iterator it = collection.begin();
+ it != collection.end(); ++it) {
+ ++count;
+ const AbstractRRset& rrset = *it;
+ EXPECT_EQ(rrclass, rrset.getClass());
+ EXPECT_EQ(RRTTL(3600), rrset.getTTL());
+ }
+
+ // example.org master file has SOA, NS, A, AAAA
+ EXPECT_EQ(4, count);
+}
+
+// This is a dummy class which is used in iteratorCompareDifferent test
+// to compare iterators from different RRsetCollectionBase
+// implementations.
+class MyRRsetCollection : public RRsetCollectionBase {
+public:
+ MyRRsetCollection()
+ {}
+
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType&) const {
+ return (ConstRRsetPtr());
+ }
+
+ typedef std::list<isc::dns::RRset> MyCollection;
+
+protected:
+ class MyIter : public RRsetCollectionBase::Iter {
+ public:
+ MyIter(MyCollection::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ return (*iter_);
+ }
+
+ virtual IterPtr getNext() {
+ MyCollection::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const MyIter* other_real = dynamic_cast<MyIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ MyCollection::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ MyCollection::iterator it = dummy_list_.begin();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ MyCollection::iterator it = dummy_list_.end();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+private:
+ MyCollection dummy_list_;
+};
+
+TEST_F(RRsetCollectionTest, iteratorCompareDifferent) {
+ // Create objects of two different RRsetCollectionBase
+ // implementations.
+ RRsetCollection cln1;
+ MyRRsetCollection cln2;
+
+ // Comparing two iterators from different RRsetCollectionBase
+ // implementations must not throw.
+ EXPECT_TRUE(cln2.begin() != cln1.begin());
+ EXPECT_TRUE(cln1.end() != cln2.end());
+}
+
+} // namespace
diff --git a/src/lib/dns/tests/rrttl_unittest.cc b/src/lib/dns/tests/rrttl_unittest.cc
index fe9c55c..8897102 100644
--- a/src/lib/dns/tests/rrttl_unittest.cc
+++ b/src/lib/dns/tests/rrttl_unittest.cc
@@ -20,6 +20,8 @@
#include <dns/tests/unittest_util.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace std;
using namespace isc;
using namespace isc::dns;
@@ -85,6 +87,17 @@ TEST_F(RRTTLTest, fromText) {
EXPECT_THROW(RRTTL("4294967296"), InvalidRRTTL); // must be 32-bit
}
+TEST_F(RRTTLTest, createFromText) {
+ // It returns an actual RRTT iff the given text is recognized as a
+ // valid RR TTL.
+ MaybeRRTTL maybe_ttl = RRTTL::createFromText("3600");
+ EXPECT_TRUE(maybe_ttl);
+ EXPECT_EQ(RRTTL(3600), *maybe_ttl);
+
+ maybe_ttl = RRTTL::createFromText("bad");
+ EXPECT_FALSE(maybe_ttl);
+}
+
void
checkUnit(unsigned multiply, char suffix) {
SCOPED_TRACE(string("Unit check with suffix ") + suffix);
@@ -252,6 +265,10 @@ TEST_F(RRTTLTest, gthan) {
EXPECT_FALSE(ttl_small > ttl_large);
}
+TEST_F(RRTTLTest, maxTTL) {
+ EXPECT_EQ((1u << 31) - 1, RRTTL::MAX_TTL().getValue());
+}
+
// test operator<<. We simply confirm it appends the result of toText().
TEST_F(RRTTLTest, LeftShiftOperator) {
ostringstream oss;
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 94cadec..52acb7c 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -170,6 +170,10 @@ EXTRA_DIST += tsig_verify1.spec tsig_verify2.spec tsig_verify3.spec
EXTRA_DIST += tsig_verify4.spec tsig_verify5.spec tsig_verify6.spec
EXTRA_DIST += tsig_verify7.spec tsig_verify8.spec tsig_verify9.spec
EXTRA_DIST += tsig_verify10.spec
+EXTRA_DIST += example.org
+EXTRA_DIST += broken.zone
+EXTRA_DIST += origincheck.txt
+EXTRA_DIST += omitcheck.txt
.spec.wire:
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/lib/dns/tests/testdata/broken.zone b/src/lib/dns/tests/testdata/broken.zone
new file mode 100644
index 0000000..70f4540
--- /dev/null
+++ b/src/lib/dns/tests/testdata/broken.zone
@@ -0,0 +1,3 @@
+; This should fail due to broken TTL
+; The file should _NOT_ end with EOLN
+broken. 3600X IN A 192.0.2.2 More data
\ No newline at end of file
diff --git a/src/lib/dns/tests/testdata/example.org b/src/lib/dns/tests/testdata/example.org
new file mode 100644
index 0000000..4163fc0
--- /dev/null
+++ b/src/lib/dns/tests/testdata/example.org
@@ -0,0 +1,17 @@
+example.org. 3600 IN SOA ( ; The SOA, split across lines for testing
+ ns1.example.org.
+ admin.example.org.
+ 1234
+ 3600
+ 1800
+ 2419200
+ 7200
+ )
+; Check it accepts quoted name too
+"\101xample.org." 3600 IN NS ns1.example.org.
+
+
+; Some empty lines here. They are to make sure the loader can skip them.
+www 3600 IN A 192.0.2.1 ; Test a relative name as well.
+ 3600 IN AAAA 2001:db8::1 ; And initial whitespace hanling
+ ; Here be just some space, no RRs
diff --git a/src/lib/dns/tests/testdata/omitcheck.txt b/src/lib/dns/tests/testdata/omitcheck.txt
new file mode 100644
index 0000000..580cab4
--- /dev/null
+++ b/src/lib/dns/tests/testdata/omitcheck.txt
@@ -0,0 +1 @@
+ 1H IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/origincheck.txt b/src/lib/dns/tests/testdata/origincheck.txt
new file mode 100644
index 0000000..c370ed2
--- /dev/null
+++ b/src/lib/dns/tests/testdata/origincheck.txt
@@ -0,0 +1,5 @@
+; We change the origin here. We want to check it is not propagated
+; outside of the included file.
+$ORIGIN www.example.org.
+
+@ 1H IN A 192.0.2.1
diff --git a/src/lib/dns/tests/testdata/rdata_txt_fromWire1 b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
index 2c51efe..95980db 100644
--- a/src/lib/dns/tests/testdata/rdata_txt_fromWire1
+++ b/src/lib/dns/tests/testdata/rdata_txt_fromWire1
@@ -1,9 +1,9 @@
#
# various kinds of TXT RDATA stored in an input buffer
#
-# Valid RDATA for "Test String"
+# Valid RDATA for "Test-String"
#
# RDLENGHT=12 bytes
00 0c
-# T e s t S t r i n g
- 0b 54 65 73 74 20 53 74 72 69 6e 67
+# T e s t - S t r i n g
+ 0b 54 65 73 74 2d 53 74 72 69 6e 67
diff --git a/src/lib/dns/tests/tsigkey_unittest.cc b/src/lib/dns/tests/tsigkey_unittest.cc
index 20ee802..c1367be 100644
--- a/src/lib/dns/tests/tsigkey_unittest.cc
+++ b/src/lib/dns/tests/tsigkey_unittest.cc
@@ -251,6 +251,15 @@ TEST_F(TSIGKeyRingTest, find) {
const TSIGKeyRing::FindResult result3 = keyring.find(key_name, md5_name);
EXPECT_EQ(TSIGKeyRing::NOTFOUND, result3.code);
EXPECT_EQ(static_cast<const TSIGKey*>(NULL), result3.key);
+
+ // But with just the name it should work
+ const TSIGKeyRing::FindResult result4(keyring.find(key_name));
+ EXPECT_EQ(TSIGKeyRing::SUCCESS, result4.code);
+ EXPECT_EQ(key_name, result4.key->getKeyName());
+ EXPECT_EQ(TSIGKey::HMACSHA256_NAME(), result4.key->getAlgorithmName());
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, secret, secret_len,
+ result4.key->getSecret(),
+ result4.key->getSecretLength());
}
TEST_F(TSIGKeyRingTest, findFromSome) {
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
new file mode 100644
index 0000000..dbe204d
--- /dev/null
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const char* const soa_txt = "ns.example.com. root.example.com. 0 0 0 0 0";
+const char* const ns_txt1 = "ns.example.com.";
+const char* const ns_a_txt1 = "192.0.2.1";
+const char* const ns_txt2 = "ns2.example.com.";
+const char* const ns_a_txt2 = "192.0.2.2";
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+ ZoneCheckerTest() :
+ zname_("example.com"), zclass_(RRClass::IN()),
+ soa_(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60))),
+ ns_(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60))),
+ callbacks_(boost::bind(&ZoneCheckerTest::callback, this, _1, true),
+ boost::bind(&ZoneCheckerTest::callback, this, _1, false))
+ {
+ std::stringstream ss;
+ ss << "example.com. 60 IN SOA " << soa_txt << "\n";
+ ss << "example.com. 60 IN NS " << ns_txt1 << "\n";
+ ss << "ns.example.com. 60 IN A " << ns_a_txt1 << "\n";
+ ss << "ns2.example.com. 60 IN A " << ns_a_txt2 << "\n";
+ rrsets_.reset(new RRsetCollection(ss, zname_, zclass_));
+ }
+
+public:
+ // This one is passed to boost::bind. Some compilers seem to require
+ // it be public.
+ void callback(const std::string& reason, bool is_error) {
+ if (is_error) {
+ errors_.push_back(reason);
+ } else {
+ warns_.push_back(reason);
+ }
+ }
+
+protected:
+ // Check stored issue messages with expected ones. Clear vectors so
+ // the caller can check other cases.
+ void checkIssues() {
+ EXPECT_EQ(expected_errors_.size(), errors_.size());
+ for (int i = 0; i < std::min(expected_errors_.size(), errors_.size());
+ ++i) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, errors_[0].find(expected_errors_[0]))
+ << "actual message: " << errors_[0] << " expected: " <<
+ expected_errors_[0];
+ }
+ EXPECT_EQ(expected_warns_.size(), warns_.size());
+ for (int i = 0; i < std::min(expected_warns_.size(), warns_.size());
+ ++i) {
+ EXPECT_EQ(0, warns_[0].find(expected_warns_[0]))
+ << "actual message: " << warns_[0] << " expected: " <<
+ expected_warns_[0];
+ }
+
+ errors_.clear();
+ expected_errors_.clear();
+ warns_.clear();
+ expected_warns_.clear();
+ }
+
+ const Name zname_;
+ const RRClass zclass_;
+ boost::scoped_ptr<RRsetCollection> rrsets_;
+ RRsetPtr soa_;
+ RRsetPtr ns_;
+ std::vector<std::string> errors_;
+ std::vector<std::string> warns_;
+ std::vector<std::string> expected_errors_;
+ std::vector<std::string> expected_warns_;
+ ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+ // Checking a valid case. No errors or warnings should be reported.
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Multiple NS RRs are okay.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_->addRdata(generic::NS(ns_txt1));
+ ns_->addRdata(generic::NS(ns_txt2));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+ // If the zone has no SOA it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 0 SOA records");
+ checkIssues();
+
+ // If null callback is specified, checkZone() only returns the final
+ // result.
+ ZoneCheckerCallbacks noerror_callbacks(
+ NULL, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
+ checkIssues();
+
+ // If there are more than 1 SOA RR, it's also an error.
+ errors_.clear();
+ soa_->addRdata(generic::SOA(soa_txt));
+ soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+ rrsets_->addRRset(soa_);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 2 SOA records");
+ checkIssues();
+
+ // If the SOA RRset is "empty", it's treated as an implementation
+ // (rather than operational) error and results in an exception.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+ // If the zone has no NS at origin it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has no NS records");
+ checkIssues();
+
+ // Check two buggy cases like the SOA tests
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNSData) {
+ const Name ns_name("ns.example.com");
+
+ // If a ("in-bailiwick") NS name doesn't have an address record, it's
+ // reported as a warning.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::A());
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // Same check, but disabling warning callback. Same result, but without
+ // the warning.
+ ZoneCheckerCallbacks nowarn_callbacks(
+ boost::bind(&ZoneCheckerTest::callback, this, _1, true), NULL);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
+ checkIssues();
+
+ // A tricky case: if the name matches a wildcard, it should technically
+ // be considered valid, but this checker doesn't check that far and still
+ // warns.
+ RRsetPtr wild(new RRset(Name("*.example.com"), zclass_, RRType::A(),
+ RRTTL(0)));
+ wild->addRdata(in::A("192.0.2.255"));
+ rrsets_->addRRset(wild);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // If there's a CNAME at the name instead, it's an error.
+ rrsets_->removeRRset(Name("*.example.com"), zclass_, RRType::A());
+ RRsetPtr cname(new RRset(ns_name, zclass_, RRType::CNAME(), RRTTL(60)));
+ cname->addRdata(generic::CNAME("cname.example.com"));
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't have to be A. An AAAA is enough.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ RRsetPtr aaaa(new RRset(ns_name, zclass_, RRType::AAAA(), RRTTL(60)));
+ aaaa->addRdata(in::AAAA("2001:db8::1"));
+ rrsets_->addRRset(aaaa);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Coexisting CNAME makes it error (CNAME with other record is itself
+ // invalid, but it's a different issue in this context)
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't matter if the NS name is "out of bailiwick".
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Note that if the NS name is the origin name, it should be checked
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(zname_));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDelegation) {
+ // Tests various cases where there's a zone cut due to delegation between
+ // the zone origin and the NS name. In each case the NS name doesn't have
+ // an address record.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to delegation in the middle; the check for the address
+ // record should be skipped.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr child_ns(new RRset(Name("child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut at the NS name. Same result.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(ns_name, zclass_, RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut below the NS name. The check applies.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(Name("another.ns.child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDNAME) {
+ // Similar to the above case, but the zone cut is due to DNAME. This is
+ // an invalid configuration.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to DNAME at the zone origin. This is an invalid case.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr dname(new RRset(zname_, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'example.com'");
+ checkIssues();
+
+ // Zone cut due to DNAME in the middle. Same result.
+ rrsets_->removeRRset(zname_, zclass_, RRType::DNAME());
+ dname.reset(new RRset(Name("child.example.com"), zclass_, RRType::DNAME(),
+ RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'child.example.com'");
+ checkIssues();
+
+ // A tricky case: there's also an NS at the name that has DNAME. It's
+ // prohibited per RFC6672 so we could say it's "undefined". Nevertheless,
+ // this implementation prefers the NS and skips further checks.
+ ns_.reset(new RRset(Name("child.example.com"), zclass_, RRType::NS(),
+ RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut due to DNAME at the NS name. In this case DNAME doesn't
+ // affect the NS name, so it should result in "no address record" warning.
+ rrsets_->removeRRset(dname->getName(), zclass_, RRType::DNAME());
+ rrsets_->removeRRset(ns_->getName(), zclass_, RRType::NS());
+ dname.reset(new RRset(ns_name, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+}
diff --git a/src/lib/dns/tsigkey.cc b/src/lib/dns/tsigkey.cc
index d7d60eb..e55cce3 100644
--- a/src/lib/dns/tsigkey.cc
+++ b/src/lib/dns/tsigkey.cc
@@ -51,7 +51,7 @@ namespace {
if (name == TSIGKey::HMACSHA512_NAME()) {
return (isc::cryptolink::SHA512);
}
-
+
return (isc::cryptolink::UNKNOWN_HASH);
}
}
@@ -270,6 +270,16 @@ TSIGKeyRing::remove(const Name& key_name) {
}
TSIGKeyRing::FindResult
+TSIGKeyRing::find(const Name& key_name) const {
+ TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
+ impl_->keys.find(key_name);
+ if (found == impl_->keys.end()) {
+ return (FindResult(NOTFOUND, NULL));
+ }
+ return (FindResult(SUCCESS, &((*found).second)));
+}
+
+TSIGKeyRing::FindResult
TSIGKeyRing::find(const Name& key_name, const Name& algorithm_name) const {
TSIGKeyRingImpl::TSIGKeyMap::const_iterator found =
impl_->keys.find(key_name);
diff --git a/src/lib/dns/tsigkey.h b/src/lib/dns/tsigkey.h
index 1bbd3fe..b10660c 100644
--- a/src/lib/dns/tsigkey.h
+++ b/src/lib/dns/tsigkey.h
@@ -327,6 +327,27 @@ public:
/// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
///
/// It searches the internal storage for a \c TSIGKey whose name is
+ /// \c key_name.
+ /// It returns the result in the form of a \c FindResult
+ /// object as follows:
+ /// - \c code: \c SUCCESS if a key is found; otherwise \c NOTFOUND.
+ /// - \c key: A pointer to the found \c TSIGKey object if one is found;
+ /// otherwise \c NULL.
+ ///
+ /// The pointer returned in the \c FindResult object is only valid until
+ /// the corresponding key is removed from the key ring.
+ /// The caller must ensure that the key is held in the key ring while
+ /// it needs to refer to it, or it must make a local copy of the key.
+ ///
+ /// This method never throws an exception.
+ ///
+ /// \param key_name The name of the key to be found.
+ /// \return A \c FindResult object enclosing the search result (see above).
+ FindResult find(const Name& key_name) const;
+
+ /// Find a \c TSIGKey for the given name in the \c TSIGKeyRing.
+ ///
+ /// It searches the internal storage for a \c TSIGKey whose name is
/// \c key_name and that uses the hash algorithm identified by
/// \c algorithm_name.
/// It returns the result in the form of a \c FindResult
@@ -346,6 +367,7 @@ public:
/// \param algorithm_name The name of the algorithm of the found key.
/// \return A \c FindResult object enclosing the search result (see above).
FindResult find(const Name& key_name, const Name& algorithm_name) const;
+
private:
struct TSIGKeyRingImpl;
TSIGKeyRingImpl* impl_;
diff --git a/src/lib/dns/zone_checker.cc b/src/lib/dns/zone_checker.cc
new file mode 100644
index 0000000..aa307d2
--- /dev/null
+++ b/src/lib/dns/zone_checker.cc
@@ -0,0 +1,196 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+
+namespace isc {
+namespace dns {
+
+namespace {
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+ return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callback) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::SOA());
+ size_t count = 0;
+ if (rrset) {
+ for (RdataIteratorPtr rit = rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next(), ++count) {
+ if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+ NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+ }
+ }
+ if (count == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+ }
+ }
+ if (count != 1) {
+ callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
+ lexical_cast<string>(count) + " SOA records");
+ }
+}
+
+// Check if a target name is beyond zone cut, either due to delegation or
+// DNAME. Note that DNAME works on the origin but not on the name itself,
+// while delegation works on the name itself (but the NS at the origin is not
+// delegation).
+ConstRRsetPtr
+findZoneCut(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
+ const unsigned int origin_count = zone_name.getLabelCount();
+ const unsigned int target_count = target_name.getLabelCount();
+ assert(origin_count <= target_count);
+
+ for (unsigned int l = origin_count; l <= target_count; ++l) {
+ const Name& mid_name = (l == target_count) ? target_name :
+ target_name.split(target_count - l);
+
+ ConstRRsetPtr found;
+ if (l != origin_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
+ NULL) {
+ return (found);
+ }
+ if (l != target_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
+ != NULL) {
+ return (found);
+ }
+ }
+ return (ConstRRsetPtr());
+}
+
+// Check if each "in-zone" NS name has an address record, identifying some
+// error cases.
+void
+checkNSNames(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
+ if (ns_rrset->getRdataCount() == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+ }
+
+ for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ const rdata::generic::NS* ns_data =
+ dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
+ if (ns_data == NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+ }
+ const Name& ns_name = ns_data->getNSName();
+ const NameComparisonResult::NameRelation reln =
+ ns_name.compare(zone_name).getRelation();
+ if (reln != NameComparisonResult::EQUAL &&
+ reln != NameComparisonResult::SUBDOMAIN) {
+ continue; // not in the zone. we can ignore it.
+ }
+
+ // Check if there's a zone cut between the origin and the NS name.
+ ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
+ zone_rrsets, ns_name);
+ if (cut_rrset) {
+ if (cut_rrset->getType() == RRType::NS()) {
+ continue; // delegation; making the NS name "out of zone".
+ } else if (cut_rrset->getType() == RRType::DNAME()) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is " +
+ "below a DNAME '" +
+ cut_rrset->getName().toText(true) +
+ "' (illegal per RFC6672)");
+ continue;
+ } else {
+ assert(false);
+ }
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is a CNAME " +
+ "(illegal per RFC2181)");
+ continue;
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
+ zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
+ callbacks.warn("zone " + zoneText(zone_name, zone_class) +
+ ": NS has no address records (A or AAAA)");
+ }
+ }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callbacks) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::NS());
+ if (rrset == NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": has no NS records");
+ return;
+ }
+ checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
+}
+
+// The following is a simple wrapper of checker callback so checkZone()
+// can also remember any critical errors.
+void
+errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
+ bool* had_error) {
+ *had_error = true;
+ callbacks->error(reason);
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks) {
+ bool had_error = false;
+ ZoneCheckerCallbacks my_callbacks(
+ boost::bind(errorWrapper, _1, &callbacks, &had_error),
+ boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
+
+ checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
+ checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
+
+ return (!had_error);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/zone_checker.h b/src/lib/dns/zone_checker.h
new file mode 100644
index 0000000..dfb4946
--- /dev/null
+++ b/src/lib/dns/zone_checker.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <boost/function.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+/// \brief Set of callbacks used in zone checks.
+///
+/// Objects of this class are expected to be passed to \c checkZone().
+class ZoneCheckerCallbacks {
+public:
+ /// \brief Functor type of the callback on some issue (error or warning).
+ ///
+ /// Its parameter indicates the reason for the corresponding issue.
+ typedef boost::function<void(const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor.
+ ///
+ /// Either or both of the callbacks can be empty, in which case the
+ /// corresponding callback will be effectively no-operation. This can be
+ /// used, for example, when the caller of \c checkZone() is only
+ /// interested in the final result. Note that a \c NULL pointer will be
+ /// implicitly converted to an empty functor object, so passing \c NULL
+ /// suffices.
+ ///
+ /// \throw none
+ ///
+ /// \param error_callback Callback functor to be called on critical errors.
+ /// \param warn_callback Callback functor to be called on non critical
+ /// issues.
+ ZoneCheckerCallbacks(const IssueCallback& error_callback,
+ const IssueCallback& warn_callback) :
+ error_callback_(error_callback), warn_callback_(warn_callback)
+ {}
+
+ /// \brief Call the callback for a critical error.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the error.
+ void error(const std::string& reason) const {
+ if (!error_callback_.empty()) {
+ error_callback_(reason);
+ }
+ }
+
+ /// \brief Call the callback for a non critical issue.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the issue.
+ void warn(const std::string& reason) const {
+ if (!warn_callback_.empty())
+ warn_callback_(reason);
+ }
+
+private:
+ IssueCallback error_callback_;
+ IssueCallback warn_callback_;
+};
+
+/// \brief Perform basic integrity checks on zone RRsets.
+///
+/// This function performs some lightweight checks on zone's SOA and (apex)
+/// NS records. Here, lightweight means it doesn't require traversing
+/// the entire zone, and should be expected to complete reasonably quickly
+/// regardless of the size of the zone.
+///
+/// It distinguishes "critical" errors and other undesirable issues:
+/// the former should be interpreted as the resulting zone shouldn't be used
+/// further, e.g, by an authoritative server implementation; the latter means
+/// the issues are better to be addressed but are not necessarily considered
+/// to make the zone invalid. Critical errors are reported via the
+/// \c error() method of \c callbacks, and non critical issues are reported
+/// via its \c warn() method.
+///
+/// Specific checks performed by this function is as follows. Failure of
+/// a check is considered a critical error unless noted otherwise:
+/// - There is exactly one SOA RR at the zone apex.
+/// - There is at least one NS RR at the zone apex.
+/// - For each apex NS record, if the NS name (the RDATA of the record) is
+/// in the zone (i.e., it's a subdomain of the zone origin and above any
+/// zone cut due to delegation), check the following:
+/// - the NS name should have an address record (AAAA or A). Failure of
+/// this check is considered a non critical issue.
+/// - the NS name does not have a CNAME. This is prohibited by Section
+/// 10.3 of RFC 2181.
+/// - the NS name is not subject to DNAME substitution. This is prohibited
+/// by Section 4 of RFC 6672.
+/// .
+///
+/// In addition, when the check is completed without any critical error, this
+/// function guarantees that RRsets for the SOA and (apex) NS stored in the
+/// passed RRset collection have the expected type of Rdata objects,
+/// i.e., generic::SOA and generic::NS, respectively. (This is normally
+/// expected to be the case, but not guaranteed by the API).
+///
+/// As for the check on the existence of AAAA or A records for NS names,
+/// it should be noted that BIND 9 treats this as a critical error.
+/// It's not clear whether it's an implementation dependent behavior or
+/// based on the protocol standard (it looks like the former), but to make
+/// it sure we need to confirm there is even no wildcard match for the names.
+/// This should be a very rare configuration, and more expensive to detect,
+/// so we do not check this condition, and treat this case as a non critical
+/// issue.
+///
+/// This function indicates the result of the checks (whether there is a
+/// critical error) via the return value: It returns \c true if there is no
+/// critical error and returns \c false otherwise. It doesn't throw an
+/// exception on encountering an error so that it can report as many errors
+/// as possible in a single call. If an exception is a better way to signal
+/// the error, the caller can pass a callback object that throws from its
+/// \c error() method.
+///
+/// This function can still throw an exception if it finds a really bogus
+/// condition that is most likely to be an implementation bug of the caller.
+/// Such cases include when an RRset contained in the RRset collection is
+/// empty.
+///
+/// \throw Unexpected Conditions that suggest a caller's bug (see the
+/// description)
+///
+/// \param zone_name The name of the zone to be checked
+/// \param zone_class The RR class of the zone to be checked
+/// \param zone_rrsets The collection of RRsets of the zone
+/// \param callbacks Callback object used to report errors and issues
+///
+/// \return \c true if no critical errors are found; \c false otherwise.
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks);
+
+} // namespace dns
+} // namespace isc
+#endif // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 6207655..56918e9 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -31,6 +31,7 @@ libb10_log_la_SOURCES += message_initializer.cc message_initializer.h
libb10_log_la_SOURCES += message_reader.cc message_reader.h
libb10_log_la_SOURCES += message_types.h
libb10_log_la_SOURCES += output_option.cc output_option.h
+libb10_log_la_SOURCES += buffer_appender_impl.cc buffer_appender_impl.h
EXTRA_DIST = README
EXTRA_DIST += logimpl_messages.mes
diff --git a/src/lib/log/README b/src/lib/log/README
index 3693abb..2a7af28 100644
--- a/src/lib/log/README
+++ b/src/lib/log/README
@@ -338,7 +338,8 @@ Variant #1, Used by Production Programs
---------------------------------------
void isc::log::initLogger(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
This is the call that should be used by production programs:
@@ -359,6 +360,17 @@ file
The name of a local message file. This will be read and its definitions used
to replace the compiled-in text of the messages.
+buffer
+If set to true, initial log messages will be internally buffered, until the
+first time a logger specification is processed. This way the program can
+use logging before even processing its logging configuration. As soon as any
+specification is processed (even an empty one), the buffered log messages will
+be flushed according to the specification. Note that if this option is used,
+the program SHOULD call one of the LoggerManager's process() calls (if you
+are using the built-in logging configuration handling in ModuleCCSession,
+this is automatically handled). If the program exits before this is done,
+all log messages are dumped in a raw format to stdout (so that no messages
+get lost).
Variant #2, Used by Unit Tests
------------------------------
diff --git a/src/lib/log/buffer_appender_impl.cc b/src/lib/log/buffer_appender_impl.cc
new file mode 100644
index 0000000..8899c4f
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.cc
@@ -0,0 +1,96 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loglevel.h>
+#include <boost/scoped_ptr.hpp>
+#include <cstdio>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+BufferAppender::~BufferAppender() {
+ // If there is anything left in the buffer,
+ // it means no reconfig has been done, and
+ // we can assume the logging system was either
+ // never setup, or broke while doing so.
+ // So dump all that is left to stdout
+ try {
+ flushStdout();
+ } catch (...) {
+ // Ok if we can't even seem to dump to stdout, never mind.
+ }
+}
+
+void
+BufferAppender::flushStdout() {
+ // This does not show a bit of information normal log messages
+ // do, so perhaps we should try and setup a new logger here
+ // However, as this is called from a destructor, it may not
+ // be a good idea; as we can't reliably know whether in what
+ // state the logger instance is now (or what the specific logger's
+ // settings were).
+ LogEventList::const_iterator it;
+ for (it = stored_.begin(); it != stored_.end(); ++it) {
+ const std::string level(it->first);
+ LogEventPtr event(it->second);
+ std::printf("%s [%s]: %s\n", level.c_str(),
+ event->getLoggerName().c_str(),
+ event->getMessage().c_str());
+ }
+ stored_.clear();
+}
+
+void
+BufferAppender::flush() {
+ LogEventList stored_copy;
+ stored_.swap(stored_copy);
+
+ LogEventList::const_iterator it;
+ for (it = stored_copy.begin(); it != stored_copy.end(); ++it) {
+ LogEventPtr event(it->second);
+ log4cplus::Logger logger =
+ log4cplus::Logger::getInstance(event->getLoggerName());
+
+ logger.log(event->getLogLevel(), event->getMessage());
+ }
+ flushed_ = true;
+}
+
+size_t
+BufferAppender::getBufferSize() const {
+ return (stored_.size());
+}
+
+void
+BufferAppender::append(const log4cplus::spi::InternalLoggingEvent& event) {
+ if (flushed_) {
+ isc_throw(LogBufferAddAfterFlush,
+ "Internal log buffer has been flushed already");
+ }
+ // get a clone, and put the pointer in a shared_ptr in the list
+ std::auto_ptr<log4cplus::spi::InternalLoggingEvent> event_aptr =
+ event.clone();
+ // Also store the string representation of the log level, to be
+ // used in flushStdout if necessary
+ stored_.push_back(LevelAndEvent(
+ log4cplus::LogLevelManager().toString(event.getLogLevel()),
+ LogEventPtr(event_aptr.release())));
+}
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
diff --git a/src/lib/log/buffer_appender_impl.h b/src/lib/log/buffer_appender_impl.h
new file mode 100644
index 0000000..00d3633
--- /dev/null
+++ b/src/lib/log/buffer_appender_impl.h
@@ -0,0 +1,118 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef LOG_BUFFER_H
+#define LOG_BUFFER_H
+
+#include <exceptions/exceptions.h>
+
+#include <log4cplus/logger.h>
+#include <log4cplus/spi/loggingevent.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace log {
+namespace internal {
+
+/// \brief Buffer add after flush
+///
+/// This exception is thrown if the log buffer's add() method
+/// is called after the log buffer has been flushed; the buffer
+/// is only supposed to be used once (until the first time a
+/// logger specification is processed)
+class LogBufferAddAfterFlush : public isc::Exception {
+public:
+ LogBufferAddAfterFlush(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what)
+ {}
+};
+
+/// Convenience typedef for a pointer to a log event
+typedef boost::shared_ptr<log4cplus::spi::InternalLoggingEvent> LogEventPtr;
+
+/// Convenience typedef for a pair string/logeventptr, the string representing
+/// the logger level, as returned by LogLevelManager::toString() at the
+/// time of initial logging
+typedef std::pair<std::string, LogEventPtr> LevelAndEvent;
+
+/// Convenience typedef for a vector of LevelAndEvent instances
+typedef std::vector<LevelAndEvent> LogEventList;
+
+/// \brief Buffering Logger Appender
+///
+/// This class can be set as an Appender for log4cplus loggers,
+/// and is used to store logging events; it simply keeps any
+/// event that is passed to \c append(), and will replay them to the
+/// logger that they were originally created for when \c flush() is
+/// called.
+///
+/// The idea is that initially, a program may want to do some logging,
+/// but does not know where to yet (for instance because it has yet to
+/// read and parse its configuration). Any log messages before this time
+/// would normally go to some default (say, stdout), and be lost in the
+/// real logging destination. By buffering them (and flushing them once
+/// the logger has been configured), these log messages are kept in a
+/// consistent place, and are not lost.
+///
+/// Given this goal, this class has an extra check; it will raise
+/// an exception if \c append() is called after flush().
+///
+/// If the BufferAppender instance is destroyed before being flushed,
+/// it will dump any event it has left to stdout.
+class BufferAppender : public log4cplus::Appender {
+public:
+ /// \brief Constructor
+ ///
+ /// Constructs a BufferAppender that buffers log evens
+ BufferAppender() : flushed_(false) {}
+
+ /// \brief Destructor
+ ///
+ /// Any remaining events are flushed to stdout (there should
+ /// only be any events remaining if flush() was never called)
+ virtual ~BufferAppender();
+
+ /// \brief Close the appender
+ ///
+ /// This class has no specialized handling for this method
+ virtual void close() {}
+
+ /// \brief Flush the internal buffer
+ ///
+ /// Events that have been stored (after calls to \c append()
+ /// are replayed to the logger. Should only be called after
+ /// new appenders have been set to the logger.
+ void flush();
+
+ /// \brief Returns the number of stored logging events
+ ///
+ /// Mainly useful for testing
+ size_t getBufferSize() const;
+
+protected:
+ virtual void append(const log4cplus::spi::InternalLoggingEvent& event);
+private:
+ /// \brief Helper for the destructor, flush events to stdout
+ void flushStdout();
+
+ LogEventList stored_;
+ bool flushed_;
+};
+
+} // end namespace internal
+} // end namespace log
+} // end namespace isc
+
+#endif // LOG_BUFFER_H
+
diff --git a/src/lib/log/log_messages.mes b/src/lib/log/log_messages.mes
index f150f39..4bc8c98 100644
--- a/src/lib/log/log_messages.mes
+++ b/src/lib/log/log_messages.mes
@@ -97,7 +97,7 @@ There may be several reasons why this message may appear:
- The message ID has been mis-spelled in the local message file.
- The program outputting the message may not use that particular message
-(e.g. it originates in a module not used by the program.)
+(e.g. it originates in a module not used by the program).
- The local file was written for an earlier version of the BIND 10 software
and the later version no longer generates that message.
@@ -131,7 +131,7 @@ version of BIND 10.
% LOG_READING_LOCAL_FILE reading local message file %1
This is an informational message output by BIND 10 when it starts to read
a local message file. (A local message file may replace the text of
-one of more messages; the ID of the message will not be changed though.)
+one or more messages; the ID of the message will not be changed though.)
% LOG_READ_ERROR error reading from message file %1: %2
The specified error was encountered reading from the named message file.
diff --git a/src/lib/log/logger_manager.cc b/src/lib/log/logger_manager.cc
index 8431c2e..77893d0 100644
--- a/src/lib/log/logger_manager.cc
+++ b/src/lib/log/logger_manager.cc
@@ -94,7 +94,7 @@ LoggerManager::processEnd() {
void
LoggerManager::init(const std::string& root, isc::log::Severity severity,
- int dbglevel, const char* file)
+ int dbglevel, const char* file, bool buffer)
{
// Load in the messages declared in the program and registered by
// statically-declared MessageInitializer objects.
@@ -114,7 +114,9 @@ LoggerManager::init(const std::string& root, isc::log::Severity severity,
// Initialize the implementation logging. After this point, some basic
// logging has been set up and messages can be logged.
- LoggerManagerImpl::init(severity, dbglevel);
+ // However, they will not appear until a logging specification has been
+ // processed (or the program exits), see TODO
+ LoggerManagerImpl::init(severity, dbglevel, buffer);
setLoggingInitialized();
// Check if there were any duplicate message IDs in the default dictionary
diff --git a/src/lib/log/logger_manager.h b/src/lib/log/logger_manager.h
index 63699c9..da49ae4 100644
--- a/src/lib/log/logger_manager.h
+++ b/src/lib/log/logger_manager.h
@@ -76,6 +76,21 @@ public:
processEnd();
}
+ /// \brief Process 'empty' specification
+ ///
+ /// This will disable any existing output options, and set
+ /// the logging to go to the built-in default (stdout).
+ /// If the logger has been initialized with buffering enabled,
+ /// all log messages up to now shall be printed to stdout.
+ ///
+ /// This is mainly useful in scenarios where buffering is needed,
+ /// but it turns out there are no logging specifications to
+ /// handle.
+ void process() {
+ processInit();
+ processEnd();
+ }
+
/// \brief Run-Time Initialization
///
/// Performs run-time initialization of the logger system, in particular
@@ -91,13 +106,18 @@ public:
/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file. This must be NULL if there
/// is no local message file.
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void init(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
/// \brief Reset logging
///
- /// Resets logging to whatever was set in the call to init().
+ /// Resets logging to whatever was set in the call to init(), expect for
+ /// the buffer option.
static void reset();
/// \brief Read local message file
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 1af40c1..711eae9 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <algorithm>
#include <iostream>
@@ -21,14 +23,17 @@
#include <log4cplus/consoleappender.h>
#include <log4cplus/fileappender.h>
#include <log4cplus/syslogappender.h>
+#include <log4cplus/helpers/loglog.h>
#include <log/logger.h>
+#include <log/logger_support.h>
#include <log/logger_level_impl.h>
#include <log/logger_manager.h>
#include <log/logger_manager_impl.h>
#include <log/log_messages.h>
#include <log/logger_name.h>
#include <log/logger_specification.h>
+#include <log/buffer_appender_impl.h>
using namespace std;
@@ -40,19 +45,24 @@ namespace log {
// passed back to the parent) and resets the root logger to logging
// informational messages. (This last is not a log4cplus default, so we have to
// explicitly reset the logging severity.)
-
void
LoggerManagerImpl::processInit() {
+ storeBufferAppenders();
+
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
initRootLogger();
}
+// Flush the BufferAppenders at the end of processing a new specification
+void
+LoggerManagerImpl::processEnd() {
+ flushBufferAppenders();
+}
+
// Process logging specification. Set up the common states then dispatch to
// add output specifications.
-
void
LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
-
log4cplus::Logger logger = log4cplus::Logger::getInstance(
expandLoggerName(spec.getName()));
@@ -65,8 +75,7 @@ LoggerManagerImpl::processSpecification(const LoggerSpecification& spec) {
// Output options given?
if (spec.optionCount() > 0) {
-
- // Yes, so replace all appenders for this logger.
+ // Replace all appenders for this logger.
logger.removeAllAppenders();
// Now process output specifications.
@@ -134,7 +143,17 @@ LoggerManagerImpl::createFileAppender(log4cplus::Logger& logger,
logger.addAppender(fileapp);
}
-// Syslog appender.
+void
+LoggerManagerImpl::createBufferAppender(log4cplus::Logger& logger) {
+ log4cplus::SharedAppenderPtr bufferapp(new internal::BufferAppender());
+ bufferapp->setName("buffer");
+ logger.addAppender(bufferapp);
+ // Since we do not know at what level the loggers will end up
+ // running, set it to the highest for now
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+}
+
+// Syslog appender.
void
LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt)
@@ -147,10 +166,10 @@ LoggerManagerImpl::createSyslogAppender(log4cplus::Logger& logger,
// One-time initialization of the log4cplus system
-
void
-LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel,
+ bool buffer)
+{
// Set up basic configurator. This attaches a ConsoleAppender to the
// root logger with suitable output. This is used until we we have
// actually read the logging configuration, in which case the output
@@ -161,25 +180,32 @@ LoggerManagerImpl::init(isc::log::Severity severity, int dbglevel) {
// Add the additional debug levels
LoggerLevelImpl::init();
- reset(severity, dbglevel);
+ initRootLogger(severity, dbglevel, buffer);
}
// Reset logging to default configuration. This closes all appenders
// and resets the root logger to output INFO messages to the console.
// It is principally used in testing.
void
-LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel) {
-
+LoggerManagerImpl::reset(isc::log::Severity severity, int dbglevel)
+{
// Initialize the root logger
initRootLogger(severity, dbglevel);
}
// Initialize the root logger
void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
- int dbglevel)
+ int dbglevel, bool buffer)
{
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+ // Disable log4cplus' own logging, unless --enable-debug was
+ // specified to configure. Note that this does not change
+ // LogLog's levels (that is still just INFO).
+#ifndef ENABLE_DEBUG
+ log4cplus::helpers::LogLog::getLogLog()->setQuietMode(true);
+#endif
+
// Set the log4cplus root to not output anything - effectively we are
// ignoring it.
log4cplus::Logger::getRoot().setLogLevel(log4cplus::OFF_LOG_LEVEL);
@@ -191,19 +217,19 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
b10root.setLogLevel(LoggerLevelImpl::convertFromBindLevel(
Level(severity, dbglevel)));
- // Set the BIND 10 root to use a console logger.
- OutputOption opt;
- createConsoleAppender(b10root, opt);
+ if (buffer) {
+ createBufferAppender(b10root);
+ } else {
+ OutputOption opt;
+ createConsoleAppender(b10root, opt);
+ }
}
-// Set the the "console" layout for the given appenders. This layout includes
-// a date/time and the name of the logger.
-
void LoggerManagerImpl::setConsoleAppenderLayout(
log4cplus::SharedAppenderPtr& appender)
{
// Create the pattern we want for the output - local time.
- string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c] %m\n";
+ string pattern = "%D{%Y-%m-%d %H:%M:%S.%q} %-5p [%c/%i] %m\n";
// Finally the text of the message
auto_ptr<log4cplus::Layout> layout(new log4cplus::PatternLayout(pattern));
@@ -225,5 +251,31 @@ void LoggerManagerImpl::setSyslogAppenderLayout(
appender->setLayout(layout);
}
+void LoggerManagerImpl::storeBufferAppenders() {
+ // Walk through all loggers, and find any buffer appenders there
+ log4cplus::LoggerList loggers = log4cplus::Logger::getCurrentLoggers();
+ log4cplus::LoggerList::iterator it;
+ for (it = loggers.begin(); it != loggers.end(); ++it) {
+ log4cplus::SharedAppenderPtr buffer_appender =
+ it->getAppender("buffer");
+ if (buffer_appender) {
+ buffer_appender_store_.push_back(buffer_appender);
+ }
+ }
+}
+
+void LoggerManagerImpl::flushBufferAppenders() {
+ std::vector<log4cplus::SharedAppenderPtr> copy;
+ buffer_appender_store_.swap(copy);
+
+ std::vector<log4cplus::SharedAppenderPtr>::iterator it;
+ for (it = copy.begin(); it != copy.end(); ++it) {
+ internal::BufferAppender* app =
+ dynamic_cast<internal::BufferAppender*>(it->get());
+ assert(app != NULL);
+ app->flush();
+ }
+}
+
} // namespace log
} // namespace isc
diff --git a/src/lib/log/logger_manager_impl.h b/src/lib/log/logger_manager_impl.h
index 2bce655..bcb2fc7 100644
--- a/src/lib/log/logger_manager_impl.h
+++ b/src/lib/log/logger_manager_impl.h
@@ -51,15 +51,14 @@ class LoggerManagerImpl {
public:
/// \brief Constructor
- LoggerManagerImpl()
- {}
+ LoggerManagerImpl() {}
/// \brief Initialize Processing
///
/// This resets the hierachy of loggers back to their defaults. This means
/// that all non-root loggers (if they exist) are set to NOT_SET, and the
/// root logger reset to logging informational messages.
- static void processInit();
+ void processInit();
/// \brief Process Specification
///
@@ -71,8 +70,7 @@ public:
/// \brief End Processing
///
/// Terminates the processing of the logging specifications.
- static void processEnd()
- {}
+ void processEnd();
/// \brief Implementation-specific initialization
///
@@ -87,8 +85,11 @@ public:
///
/// \param severity Severity to be associated with this logger
/// \param dbglevel Debug level associated with the root logger
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void init(isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0);
+ int dbglevel = 0, bool buffer = false);
/// \brief Reset logging
///
@@ -132,15 +133,27 @@ private:
static void createSyslogAppender(log4cplus::Logger& logger,
const OutputOption& opt);
+ /// \brief Create buffered appender
+ ///
+ /// Appends an object to the logger that will store the log events sent
+ /// to the logger. These log messages are replayed to the logger in
+ /// processEnd().
+ ///
+ /// \param logger Log4cplus logger to which the appender must be attached.
+ static void createBufferAppender(log4cplus::Logger& logger);
+
/// \brief Set default layout and severity for root logger
///
- /// Initializes the root logger to BIND 10 defaults - console output and
- /// the passed severity/debug level.
+ /// Initializes the root logger to BIND 10 defaults - console or buffered
+ /// output and the passed severity/debug level.
///
/// \param severity Severity of messages that the logger should output.
/// \param dbglevel Debug level if severity = DEBUG
+ /// \param buffer If true, all log messages will be buffered until one of
+ /// the \c process() methods is called. If false, initial logging
+ /// shall go to the default output (i.e. stdout)
static void initRootLogger(isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0);
+ int dbglevel = 0, bool buffer = false);
/// \brief Set layout for console appender
///
@@ -161,6 +174,25 @@ private:
///
/// \param appender Appender for which this pattern is to be set.
static void setSyslogAppenderLayout(log4cplus::SharedAppenderPtr& appender);
+
+ /// \brief Store all buffer appenders
+ ///
+ /// When processing a new specification, this method can be used
+ /// to keep a list of the buffer appenders; the caller can then
+ /// process the specification, and call \c flushBufferAppenders()
+ /// to flush and clear the list
+ void storeBufferAppenders();
+
+ /// \brief Flush the stored buffer appenders
+ ///
+ /// This flushes the list of buffer appenders stored in
+ /// \c storeBufferAppenders(), and clears it
+ void flushBufferAppenders();
+
+ /// Only used between processInit() and processEnd(), to temporarily
+ /// store the buffer appenders in order to flush them after
+ /// processSpecification() calls have been completed
+ std::vector<log4cplus::SharedAppenderPtr> buffer_appender_store_;
};
} // namespace log
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index 2097136..cab52ad 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -46,8 +46,8 @@ setLoggingInitialized(bool state) {
void
initLogger(const string& root, isc::log::Severity severity, int dbglevel,
- const char* file) {
- LoggerManager::init(root, severity, dbglevel, file);
+ const char* file, bool buffer) {
+ LoggerManager::init(root, severity, dbglevel, file, buffer);
}
} // namespace log
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index f59be60..bd3b5b3 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -61,9 +61,13 @@ void setLoggingInitialized(bool state = true);
/// \param severity Severity at which to log
/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file.
+/// \param buffer If true, all log messages will be buffered until one of
+/// the \c process() methods is called. If false, initial logging
+/// shall go to the default output (i.e. stdout)
void initLogger(const std::string& root,
isc::log::Severity severity = isc::log::INFO,
- int dbglevel = 0, const char* file = NULL);
+ int dbglevel = 0, const char* file = NULL,
+ bool buffer = false);
} // namespace log
} // namespace isc
diff --git a/src/lib/log/tests/.gitignore b/src/lib/log/tests/.gitignore
index b0e45b9..e7e12d6 100644
--- a/src/lib/log/tests/.gitignore
+++ b/src/lib/log/tests/.gitignore
@@ -1,3 +1,5 @@
+/buffer_logger_test
+/buffer_logger_test.sh
/console_test.sh
/destination_test.sh
/init_logger_test
diff --git a/src/lib/log/tests/Makefile.am b/src/lib/log/tests/Makefile.am
index 3ac19a4..5683842 100644
--- a/src/lib/log/tests/Makefile.am
+++ b/src/lib/log/tests/Makefile.am
@@ -37,6 +37,15 @@ init_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
init_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
init_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+noinst_PROGRAMS += buffer_logger_test
+buffer_logger_test_SOURCES = buffer_logger_test.cc
+buffer_logger_test_CPPFLAGS = $(AM_CPPFLAGS)
+buffer_logger_test_LDFLAGS = $(AM_LDFLAGS)
+buffer_logger_test_LDADD = $(top_builddir)/src/lib/log/libb10-log.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/util/libb10-util.la
+buffer_logger_test_LDADD += $(top_builddir)/src/lib/exceptions/libb10-exceptions.la
+buffer_logger_test_LDADD += $(AM_LDADD) $(LOG4CPLUS_LIBS)
+
noinst_PROGRAMS += logger_lock_test
logger_lock_test_SOURCES = logger_lock_test.cc
nodist_logger_lock_test_SOURCES = log_test_messages.cc log_test_messages.h
@@ -82,11 +91,13 @@ run_unittests_SOURCES += logger_specification_unittest.cc
run_unittests_SOURCES += message_dictionary_unittest.cc
run_unittests_SOURCES += message_reader_unittest.cc
run_unittests_SOURCES += output_option_unittest.cc
+run_unittests_SOURCES += buffer_appender_unittest.cc
nodist_run_unittests_SOURCES = log_test_messages.cc log_test_messages.h
run_unittests_CPPFLAGS = $(AM_CPPFLAGS)
run_unittests_CXXFLAGS = $(AM_CXXFLAGS)
run_unittests_LDADD = $(AM_LDADD)
+run_unittests_LDADD += $(LOG4CPLUS_LIBS)
run_unittests_LDFLAGS = $(AM_LDFLAGS)
# logging initialization tests. These are put in separate programs to
@@ -124,6 +135,15 @@ check-local:
$(SHELL) $(abs_builddir)/console_test.sh
$(SHELL) $(abs_builddir)/destination_test.sh
$(SHELL) $(abs_builddir)/init_logger_test.sh
+ $(SHELL) $(abs_builddir)/buffer_logger_test.sh
$(SHELL) $(abs_builddir)/local_file_test.sh
$(SHELL) $(abs_builddir)/logger_lock_test.sh
$(SHELL) $(abs_builddir)/severity_test.sh
+
+noinst_SCRIPTS = console_test.sh
+noinst_SCRIPTS += destination_test.sh
+noinst_SCRIPTS += init_logger_test.sh
+noinst_SCRIPTS += buffer_logger_test.sh
+noinst_SCRIPTS += local_file_test.sh
+noinst_SCRIPTS += logger_lock_test.sh
+noinst_SCRIPTS += severity_test.sh
diff --git a/src/lib/log/tests/buffer_appender_unittest.cc b/src/lib/log/tests/buffer_appender_unittest.cc
new file mode 100644
index 0000000..781fcbe
--- /dev/null
+++ b/src/lib/log/tests/buffer_appender_unittest.cc
@@ -0,0 +1,146 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "config.h"
+#include <gtest/gtest.h>
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/log_messages.h>
+#include <log/buffer_appender_impl.h>
+
+#include <log4cplus/loggingmacros.h>
+#include <log4cplus/logger.h>
+#include <log4cplus/nullappender.h>
+#include <log4cplus/spi/loggingevent.h>
+
+using namespace isc::log;
+using namespace isc::log::internal;
+
+namespace isc {
+namespace log {
+
+/// \brief Specialized test class
+///
+/// In order to test append() somewhat directly, this
+/// class implements one more method (addEvent)
+class TestBufferAppender : public BufferAppender {
+public:
+ void addEvent(const log4cplus::spi::InternalLoggingEvent& event) {
+ append(event);
+ }
+};
+
+class BufferAppenderTest : public ::testing::Test {
+protected:
+ BufferAppenderTest() : buffer_appender1(new TestBufferAppender()),
+ appender1(buffer_appender1),
+ buffer_appender2(new TestBufferAppender()),
+ appender2(buffer_appender2),
+ logger(log4cplus::Logger::getInstance("buffer"))
+ {
+ logger.setLogLevel(log4cplus::TRACE_LOG_LEVEL);
+ }
+
+ ~BufferAppenderTest() {
+ // If any log messages are left, we don't care, get rid of them,
+ // by flushing them to a null appender
+ // Given the 'messages should not get lost' approach of the logging
+ // system, not flushing them to a null appender would cause them
+ // to be dumped to stdout as the test is destroyed, making
+ // unnecessarily messy test output.
+ log4cplus::SharedAppenderPtr null_appender(
+ new log4cplus::NullAppender());
+ logger.removeAllAppenders();
+ logger.addAppender(null_appender);
+ buffer_appender1->flush();
+ buffer_appender2->flush();
+ }
+
+ TestBufferAppender* buffer_appender1;
+ log4cplus::SharedAppenderPtr appender1;
+ TestBufferAppender* buffer_appender2;
+ log4cplus::SharedAppenderPtr appender2;
+ log4cplus::Logger logger;
+};
+
+// Test that log events are indeed stored, and that they are
+// flushed to the new appenders of their logger
+TEST_F(BufferAppenderTest, flush) {
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Create a Logger, log a few messages with the first appender
+ logger.addAppender(appender1);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(3, buffer_appender1->getBufferSize());
+
+ // Second buffer should still be empty
+ ASSERT_EQ(0, buffer_appender2->getBufferSize());
+
+ // Replace the appender by the second one, and call flush;
+ // this should cause all events to be moved to the second buffer
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(3, buffer_appender2->getBufferSize());
+}
+
+// Once flushed, logging new messages with the same buffer should fail
+TEST_F(BufferAppenderTest, addAfterFlush) {
+ logger.addAppender(appender1);
+ buffer_appender1->flush();
+ EXPECT_THROW(LOG4CPLUS_INFO(logger, "Foo"), LogBufferAddAfterFlush);
+ // It should not have been added
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+
+ // But logging should work again as long as a different buffer is used
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ LOG4CPLUS_INFO(logger, "Foo");
+ ASSERT_EQ(1, buffer_appender2->getBufferSize());
+}
+
+TEST_F(BufferAppenderTest, addDirectly) {
+ // A few direct calls
+ log4cplus::spi::InternalLoggingEvent event("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event);
+ ASSERT_EQ(1, buffer_appender1->getBufferSize());
+
+ // Do one from a smaller scope to make sure destruction doesn't harm
+ {
+ log4cplus::spi::InternalLoggingEvent event2("buffer",
+ log4cplus::INFO_LOG_LEVEL,
+ "Bar", "file", 123);
+ buffer_appender1->addEvent(event2);
+ }
+ ASSERT_EQ(2, buffer_appender1->getBufferSize());
+
+ // And flush them to the next
+ logger.removeAllAppenders();
+ logger.addAppender(appender2);
+ buffer_appender1->flush();
+ ASSERT_EQ(0, buffer_appender1->getBufferSize());
+ ASSERT_EQ(2, buffer_appender2->getBufferSize());
+}
+
+}
+}
diff --git a/src/lib/log/tests/buffer_logger_test.cc b/src/lib/log/tests/buffer_logger_test.cc
new file mode 100644
index 0000000..8d1b3cf
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <log/macros.h>
+#include <log/logger_support.h>
+#include <log/logger_manager.h>
+#include <log/log_messages.h>
+#include <util/interprocess_sync_null.h>
+
+using namespace isc::log;
+
+namespace {
+void usage() {
+ std::cout << "Usage: buffer_logger_test [-n]" << std::endl;
+}
+} // end unnamed namespace
+
+/// \brief Test InitLogger
+///
+/// A program used in testing the logger that initializes logging with
+/// buffering enabled, so that initial log messages are not immediately
+/// logged, but are not lost (they should be logged the moment process()
+/// is called.
+///
+/// If -n is given as an argument, process() is never called. In this
+/// case, upon exit, all leftover log messages should be printed to
+/// stdout, but without normal logging additions (such as time and
+/// logger name)
+int
+main(int argc, char** argv) {
+ bool do_process = true;
+ int opt;
+ while ((opt = getopt(argc, argv, "n")) != -1) {
+ switch (opt) {
+ case 'n':
+ do_process = false;
+ break;
+ default:
+ usage();
+ return (1);
+ }
+ }
+
+ // Note, level is INFO, so DEBUG should normally not show
+ // up. Unless process is never called (at which point it
+ // will end up in the dump at the end).
+ initLogger("buffertest", isc::log::INFO, 0, NULL, true);
+ Logger logger("log");
+ // No need for file interprocess locking in this test
+ logger.setInterprocessSync(new isc::util::InterprocessSyncNull("logger"));
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ LOG_DEBUG(logger, 50, LOG_BAD_DESTINATION).arg("debug-50");
+ LOG_INFO(logger, LOG_BAD_SEVERITY).arg("info");
+ // process should cause them to be logged
+ if (do_process) {
+ LoggerManager logger_manager;
+ logger_manager.process();
+ }
+ return (0);
+}
diff --git a/src/lib/log/tests/buffer_logger_test.sh.in b/src/lib/log/tests/buffer_logger_test.sh.in
new file mode 100755
index 0000000..696f829
--- /dev/null
+++ b/src/lib/log/tests/buffer_logger_test.sh.in
@@ -0,0 +1,61 @@
+#!/bin/sh
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# Checks that the initLogger() call uses for unit tests respects the setting of
+# the buffer value
+#
+
+testname="bufferLogger test"
+echo $testname
+
+failcount=0
+tempfile=@abs_builddir@/buffer_logger_test_tempfile_$$
+
+passfail() {
+ if [ $1 -eq 0 ]; then
+ echo " pass"
+ else
+ echo " FAIL"
+ failcount=`expr $failcount + $1`
+ fi
+}
+
+echo "1. Checking that buffer initialization works"
+
+echo -n " - Buffer including process() call: "
+cat > $tempfile << .
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+INFO [buffertest.log] LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test 2>&1 | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
+passfail $?
+
+echo -n " - Buffer excluding process() call: "
+cat > $tempfile << .
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+DEBUG [buffertest.log]: LOG_BAD_DESTINATION unrecognized log destination: debug-50
+INFO [buffertest.log]: LOG_BAD_SEVERITY unrecognized log severity: info
+.
+./buffer_logger_test -n 2>&1 | diff $tempfile -
+passfail $?
+
+
+
+# Tidy up.
+rm -f $tempfile
+
+exit $failcount
diff --git a/src/lib/log/tests/destination_test.sh.in b/src/lib/log/tests/destination_test.sh.in
index 1cfb9fb..2a73fc5f 100755
--- a/src/lib/log/tests/destination_test.sh.in
+++ b/src/lib/log/tests/destination_test.sh.in
@@ -20,6 +20,8 @@ echo $testname
failcount=0
tempfile=@abs_builddir@/destination_test_tempfile_$$
+destfile1_tmp=@abs_builddir@/destination_test_destfile_1_tmp_$$
+destfile2_tmp=@abs_builddir@/destination_test_destfile_2_tmp_$$
destfile1=@abs_builddir@/destination_test_destfile_1_$$
destfile2=@abs_builddir@/destination_test_destfile_2_$$
@@ -40,7 +42,11 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
.
rm -f $destfile1 $destfile2
-./logger_example -s error -f $destfile1 -f $destfile2
+./logger_example -s error -f $destfile1_tmp -f $destfile2_tmp
+
+# strip the pids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
echo -n " - destination 1:"
cut -d' ' -f3- $destfile1 | diff $tempfile -
@@ -50,9 +56,16 @@ echo -n " - destination 2:"
cut -d' ' -f3- $destfile2 | diff $tempfile -
passfail $?
+# Tidy up.
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
+
echo "2. Two loggers, different destinations and severities"
rm -f $destfile1 $destfile2
-./logger_example -l example -s info -f $destfile1 -l alpha -s warn -f $destfile2
+./logger_example -l example -s info -f $destfile1_tmp -l alpha -s warn -f $destfile2_tmp
+
+# strip the pids
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile1_tmp > $destfile1
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile2_tmp > $destfile2
# All output for example and example.beta should have gone to destfile1.
# Output for example.alpha should have done to destfile2.
@@ -86,6 +99,6 @@ else
fi
# Tidy up.
-rm -f $tempfile $destfile1 $destfile2
+rm -f $tempfile $destfile1_tmp $destfile2_tmp $destfile1 $destfile2
exit $failcount
diff --git a/src/lib/log/tests/init_logger_test.sh.in b/src/lib/log/tests/init_logger_test.sh.in
index 795419b..58c268a 100755
--- a/src/lib/log/tests/init_logger_test.sh.in
+++ b/src/lib/log/tests/init_logger_test.sh.in
@@ -21,6 +21,7 @@ echo $testname
failcount=0
tempfile=@abs_builddir@/init_logger_test_tempfile_$$
+destfile_tmp=@abs_builddir@/init_logger_test_destfile_tmp_$$
destfile=@abs_builddir@/init_logger_test_destfile_$$
passfail() {
@@ -45,6 +46,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
.
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=99 ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
cut -d' ' -f3- | diff $tempfile -
passfail $?
@@ -58,6 +60,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
.
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=DEBUG B10_LOGGER_DBGLEVEL=50 ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
cut -d' ' -f3- | diff $tempfile -
passfail $?
@@ -68,6 +71,7 @@ ERROR [bind10.log] LOG_DUPLICATE_MESSAGE_ID duplicate message ID (error) in comp
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
.
B10_LOGGER_DESTINATION=stdout B10_LOGGER_SEVERITY=WARN ./init_logger_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
cut -d' ' -f3- | diff $tempfile -
passfail $?
@@ -77,20 +81,23 @@ echo -n " - stdout: "
cat > $tempfile << .
FATAL [bind10.log] LOG_NO_MESSAGE_ID line fatal: message definition line found without a message ID
.
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stdout ./init_logger_test 1> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
cut -d' ' -f3- $destfile | diff $tempfile -
passfail $?
echo -n " - stderr: "
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=stderr ./init_logger_test 2> $destfile_tmp
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
cut -d' ' -f3- $destfile | diff $tempfile -
passfail $?
echo -n " - file: "
-rm -f $destfile
-B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile ./init_logger_test
+rm -f $destfile_tmp $destfile
+B10_LOGGER_SEVERITY=FATAL B10_LOGGER_DESTINATION=$destfile_tmp ./init_logger_test
+sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' < $destfile_tmp > $destfile
cut -d' ' -f3- $destfile | diff $tempfile -
passfail $?
@@ -105,6 +112,6 @@ else
fi
# Tidy up.
-rm -f $tempfile $destfile
+rm -f $tempfile $destfile_tmp $destfile
exit $failcount
diff --git a/src/lib/log/tests/local_file_test.sh.in b/src/lib/log/tests/local_file_test.sh.in
index 9b898e6..b47c7b4 100755
--- a/src/lib/log/tests/local_file_test.sh.in
+++ b/src/lib/log/tests/local_file_test.sh.in
@@ -51,7 +51,9 @@ FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
.
-./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s warn $localmes | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "2. Report error if unable to read local message file:"
@@ -66,7 +68,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
.
rm -f $localmes
-./logger_example -c stdout -s warn $localmes | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s warn $localmes | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
passfail $?
if [ $failcount -eq 0 ]; then
diff --git a/src/lib/log/tests/logger_lock_test.sh.in b/src/lib/log/tests/logger_lock_test.sh.in
index 0324499..c664c91 100755
--- a/src/lib/log/tests/logger_lock_test.sh.in
+++ b/src/lib/log/tests/logger_lock_test.sh.in
@@ -36,7 +36,8 @@ INFO [bind10.log] LOG_LOCK_TEST_MESSAGE this is a test message.
LOGGER_LOCK_TEST: UNLOCK
.
rm -f $destfile
-B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test > $destfile
+B10_LOGGER_SEVERITY=INFO B10_LOGGER_DESTINATION=stdout ./logger_lock_test | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' > $destfile
cut -d' ' -f3- $destfile | diff $tempfile -
passfail $?
diff --git a/src/lib/log/tests/severity_test.sh.in b/src/lib/log/tests/severity_test.sh.in
index 78d5050..bf47447 100755
--- a/src/lib/log/tests/severity_test.sh.in
+++ b/src/lib/log/tests/severity_test.sh.in
@@ -43,7 +43,9 @@ ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_erro
WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
.
-./logger_example -c stdout | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "2. Severity filter:"
@@ -53,7 +55,9 @@ ERROR [example] LOG_READING_LOCAL_FILE reading local message file dummy/file
FATAL [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta_fatal
ERROR [example.beta] LOG_BAD_DESTINATION unrecognized log destination: beta_error
.
-./logger_example -c stdout -s error | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s error | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
passfail $?
echo -n "3. Debug level:"
@@ -72,7 +76,9 @@ WARN [example.beta] LOG_BAD_STREAM bad log console output stream: beta_warn
INFO [example.beta] LOG_READ_ERROR error reading from message file beta: info
DEBUG [example.beta] LOG_BAD_SEVERITY unrecognized log severity: beta/25
.
-./logger_example -c stdout -s debug -d 25 | cut -d' ' -f3- | diff $tempfile -
+./logger_example -c stdout -s debug -d 25 | \
+ sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | \
+ cut -d' ' -f3- | diff $tempfile -
passfail $?
if [ $failcount -eq 0 ]; then
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 1f7006c..febeb10 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -177,8 +177,14 @@ class BaseComponent:
self._start_internal()
except Exception as e:
logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
- self.failed(None)
- raise
+ try:
+ self.failed(None)
+ finally:
+ # Even failed() can fail if this happens during initial startup
+ # time. In that case we'd rather propagate the original reason
+ # for the failure than the fact that failed() failed. So we
+ # always re-raise the original exception.
+ raise e
def stop(self):
"""
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 688ccf5..dcd9b64 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -17,11 +17,7 @@ from isc.bind10.component import Component, BaseComponent
import isc.bind10.sockcreator
from bind10_config import LIBEXECPATH
import os
-import posix
import isc.log
-from isc.log_messages.bind10_messages import *
-
-logger = isc.log.Logger("boss")
class SockCreator(BaseComponent):
"""
@@ -36,8 +32,6 @@ class SockCreator(BaseComponent):
def __init__(self, process, boss, kind, address=None, params=None):
BaseComponent.__init__(self, boss, kind)
self.__creator = None
- self.__uid = boss.uid
- self.__gid = boss.gid
def _start_internal(self):
self._boss.curproc = 'b10-sockcreator'
@@ -46,12 +40,9 @@ class SockCreator(BaseComponent):
self._boss.register_process(self.pid(), self)
self._boss.set_creator(self.__creator)
self._boss.log_started(self.pid())
- if self.__gid is not None:
- logger.info(BIND10_SETGID, self.__gid)
- posix.setgid(self.__gid)
- if self.__uid is not None:
- logger.info(BIND10_SETUID, self.__uid)
- posix.setuid(self.__uid)
+
+ # We are now ready for switching user.
+ self._boss.change_user()
def _stop_internal(self):
self.__creator.terminate()
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 18efea7..8603201 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,10 +104,10 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.__stop_process_params = None
self.__start_simple_params = None
# Pretending to be boss
- self.gid = None
- self.__gid_set = None
- self.uid = None
- self.__uid_set = None
+ self.__change_user_called = False
+
+ def change_user(self):
+ self.__change_user_called = True # just record the fact it's called
def __start(self):
"""
@@ -624,12 +624,6 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.assertTrue(process.killed)
self.assertFalse(process.terminated)
- def setgid(self, gid):
- self.__gid_set = gid
-
- def setuid(self, uid):
- self.__uid_set = uid
-
class FakeCreator:
def pid(self):
return 42
@@ -655,35 +649,19 @@ class ComponentTests(BossUtils, unittest.TestCase):
"""
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- orig_setgid = isc.bind10.special_component.posix.setgid
- orig_setuid = isc.bind10.special_component.posix.setuid
- isc.bind10.special_component.posix.setgid = self.setgid
- isc.bind10.special_component.posix.setuid = self.setuid
orig_creator = \
isc.bind10.special_component.isc.bind10.sockcreator.Creator
# Just ignore the creator call
isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
lambda path: self.FakeCreator()
component.start()
- # No gid/uid set in boss, nothing called.
- self.assertIsNone(self.__gid_set)
- self.assertIsNone(self.__uid_set)
+ self.assertTrue(self.__change_user_called)
# Doesn't do anything, but doesn't crash
component.stop()
component.kill()
component.kill(True)
- self.gid = 4200
- self.uid = 42
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- component.start()
- # This time, it get's called
- self.assertEqual(4200, self.__gid_set)
- self.assertEqual(42, self.__uid_set)
- isc.bind10.special_component.posix.setgid = orig_setgid
- isc.bind10.special_component.posix.setuid = orig_setuid
- isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
- orig_creator
class TestComponent(BaseComponent):
"""
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index aa0547b..9563cab 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -34,7 +34,7 @@ import bind10_config
import isc.log
from isc.log_messages.cfgmgr_messages import *
-logger = isc.log.Logger("cfgmgr")
+logger = isc.log.Logger("cfgmgr", buffer=True)
class ConfigManagerDataReadError(Exception):
"""This exception is thrown when there is an error while reading
@@ -224,11 +224,17 @@ class ConfigManager:
def check_logging_config(self, config):
if self.log_module_name in config:
+ # If there is logging config, apply it.
ccsession.default_logconfig_handler(config[self.log_module_name],
self.log_config_data)
+ else:
+ # If there is no logging config, we still need to trigger the
+ # handler, so make it use defaults (and flush any buffered logs)
+ ccsession.default_logconfig_handler({}, self.log_config_data)
def notify_boss(self):
"""Notifies the Boss module that the Config Manager is running"""
+ # TODO: Use a real, broadcast notification here.
self.cc.group_sendmsg({"running": "ConfigManager"}, "Boss")
def set_module_spec(self, spec):
@@ -313,11 +319,11 @@ class ConfigManager:
self.config = ConfigManagerData.read_from_file(self.data_path,
self.\
database_filename)
- self.check_logging_config(self.config.data);
except ConfigManagerDataEmpty:
# ok, just start with an empty config
self.config = ConfigManagerData(self.data_path,
self.database_filename)
+ self.check_logging_config(self.config.data);
def write_config(self):
"""Write the current configuration to the file specificied at init()"""
diff --git a/src/lib/python/isc/datasrc/Makefile.am b/src/lib/python/isc/datasrc/Makefile.am
index 84da788..28c87ac 100644
--- a/src/lib/python/isc/datasrc/Makefile.am
+++ b/src/lib/python/isc/datasrc/Makefile.am
@@ -2,7 +2,7 @@ SUBDIRS = . tests
# old data, should be removed in the near future once conversion is done
pythondir = $(pyexecdir)/isc/datasrc
-python_PYTHON = __init__.py master.py sqlite3_ds.py
+python_PYTHON = __init__.py sqlite3_ds.py
# new data
@@ -20,6 +20,7 @@ datasrc_la_SOURCES += updater_python.cc updater_python.h
datasrc_la_SOURCES += journal_reader_python.cc journal_reader_python.h
datasrc_la_SOURCES += configurableclientlist_python.cc
datasrc_la_SOURCES += configurableclientlist_python.h
+datasrc_la_SOURCES += zone_loader_python.cc zone_loader_python.h
datasrc_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
datasrc_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -35,6 +36,7 @@ EXTRA_DIST += finder_inc.cc
EXTRA_DIST += iterator_inc.cc
EXTRA_DIST += updater_inc.cc
EXTRA_DIST += journal_reader_inc.cc
+EXTRA_DIST += zone_loader_inc.cc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/datasrc/__init__.py b/src/lib/python/isc/datasrc/__init__.py
index 7ebd918..6e0f5ae 100644
--- a/src/lib/python/isc/datasrc/__init__.py
+++ b/src/lib/python/isc/datasrc/__init__.py
@@ -2,7 +2,7 @@ import sys
import os
# The datasource factory loader uses dlopen, as does python
-# for its modules. Some dynamic linkers do not play nice if
+# for its modules. Some dynamic linkers do not play nice if
# modules are not loaded with RTLD_GLOBAL, a symptom of which
# is that exceptions are not recognized by type. So to make
# sure this doesn't happen, we temporarily set RTLD_GLOBAL
@@ -31,5 +31,4 @@ else:
sys.setdlopenflags(flags)
from isc.datasrc.sqlite3_ds import *
-from isc.datasrc.master import *
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index e0c0f06..9e66016 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -88,6 +88,68 @@ Return Value(s): A tuple containing a result value and a ZoneFinder object or\n\
None\n\
";
+const char* const DataSourceClient_createZone_doc = "\
+create_zone(name) -> bool\n\
+\n\
+Create a zone in the data source.\n\
+\n\
+Creates a new (empty) zone in the data source backend, which can\n\
+subsequently be filled with data (through get_updater()).\n\
+\n\
+Note: This is a tentative API, and this method is likely to change or\n\
+be removed in the near future. For that reason, it currently provides\n\
+a default implementation that throws NotImplemented.\n\
+\n\
+Exceptions:\n\
+ NotImplemented If the datasource backend does not support direct\n\
+ zone creation.\n\
+ DataSourceError If something goes wrong in the data source while\n\
+ creating the zone, or there are some really unexpected\n\
+ errors.\n\
+\n\
+Parameters:\n\
+ name The (fully qualified) name of the zone to create\n\
+\n\
+Return Value(s): True if the zone was added, False if it already\n\
+existed\n\
+";
+
+const char* const DataSourceClient_deleteZone_doc = "\
+delete_zone(zone_name) -> bool\n\
+\n\
+Delete a zone from the data source.\n\
+\n\
+This method also checks if the specified zone exists in the data\n\
+source, and returns true/false depending on whether the zone\n\
+existed/not existed, respectively. In either case, on successful\n\
+return it ensures the data source does not contain the specified name\n\
+of the zone.\n\
+\n\
+Note: This is a tentative API, and this method is likely to change or\n\
+be removed in the near future. For that reason, it currently provides\n\
+a default implementation that throws NotImplemented. Note also that\n\
+this method does not delete other database records related to the\n\
+zone, such as zone's resource records or differences corresponding to\n\
+updates made in the zone. This is primarily for implementation\n\
+simplicity (in the currently intended usage there wouldn't be such\n\
+other data at the time of this call anyway) and due to the fact that\n\
+details of managing zones is still in flux. Once the design in this\n\
+area is fixed we may revisit the behavior.\n\
+\n\
+Exceptions:\n\
+ NotImplemented If the datasource backend does not support direct\n\
+ zone deletion.\n\
+ DataSourceError If something goes wrong in the data source while\n\
+ deleting the zone, or there are some really unexpected\n\
+ errors.\n\
+\n\
+Parameters:\n\
+ zone_name The (fully qualified) name of the zone to be deleted\n\
+\n\
+Return Value(s): true if the zone previously existed and has been\n\
+deleted by this method; false if the zone didn't exist.\n\
+";
+
const char* const DataSourceClient_getIterator_doc = "\
get_iterator(name, separate_rrs=False) -> ZoneIterator\n\
\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index 1a9ae47..9e4bd42 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -92,6 +92,67 @@ DataSourceClient_findZone(PyObject* po_self, PyObject* args) {
}
PyObject*
+DataSourceClient_createZone(PyObject* po_self, PyObject* args) {
+ s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+ PyObject* name;
+ if (PyArg_ParseTuple(args, "O!", &name_type, &name)) {
+ try {
+ if (self->client->createZone(PyName_ToName(name))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const isc::NotImplemented& ni) {
+ PyErr_SetString(getDataSourceException("NotImplemented"),
+ ni.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+ } else {
+ return (NULL);
+ }
+}
+
+PyObject*
+DataSourceClient_deleteZone(PyObject* po_self, PyObject* args) {
+ s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+ PyObject* po_name;
+ if (PyArg_ParseTuple(args, "O!", &name_type, &po_name)) {
+ try {
+ if (self->client->deleteZone(PyName_ToName(po_name))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const isc::NotImplemented& ni) {
+ PyErr_SetString(getDataSourceException("NotImplemented"),
+ ni.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+ }
+ return (NULL);
+}
+
+PyObject*
DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
PyObject* name_obj;
@@ -230,6 +291,10 @@ DataSourceClient_getJournalReader(PyObject* po_self, PyObject* args) {
PyMethodDef DataSourceClient_methods[] = {
{ "find_zone", DataSourceClient_findZone, METH_VARARGS,
DataSourceClient_findZone_doc },
+ { "create_zone", DataSourceClient_createZone, METH_VARARGS,
+ DataSourceClient_createZone_doc },
+ { "delete_zone", DataSourceClient_deleteZone, METH_VARARGS,
+ DataSourceClient_deleteZone_doc },
{ "get_iterator",
DataSourceClient_getIterator, METH_VARARGS,
DataSourceClient_getIterator_doc },
@@ -374,6 +439,16 @@ wrapDataSourceClient(DataSourceClient* client,
return (container.release());
}
+DataSourceClient&
+PyDataSourceClient_ToDataSourceClient(PyObject* client_obj) {
+ if (client_obj == NULL) {
+ isc_throw(PyCPPWrapperException,
+ "argument NULL in DataSourceClient PyObject conversion");
+ }
+ s_DataSourceClient* client = static_cast<s_DataSourceClient*>(client_obj);
+ return (*client->client);
+}
+
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/client_python.h b/src/lib/python/isc/datasrc/client_python.h
index 71aee8b..e700fde 100644
--- a/src/lib/python/isc/datasrc/client_python.h
+++ b/src/lib/python/isc/datasrc/client_python.h
@@ -44,6 +44,17 @@ wrapDataSourceClient(DataSourceClient* client,
LifeKeeper>& life_keeper = boost::shared_ptr<ClientList::
FindResult::LifeKeeper>());
+/// \brief Returns a reference to the DataSourceClient object contained
+/// in the given Python object.
+///
+/// \note The given object MUST be of type DataSourceClient; this can be
+/// checked with the right call to ParseTuple("O!")
+///
+/// \param client_obj Python object holding the DataSourceClient
+/// \return reference to the DataSourceClient object
+DataSourceClient&
+PyDataSourceClient_ToDataSourceClient(PyObject* client_obj);
+
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 533a08c..cfacc64 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -21,6 +21,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/zone_loader.h>
#include "datasrc.h"
#include "client_python.h"
@@ -29,10 +30,14 @@
#include "updater_python.h"
#include "journal_reader_python.h"
#include "configurableclientlist_python.h"
+#include "zone_loader_python.h"
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
+#include <stdexcept>
+#include <string>
+
using namespace isc::datasrc;
using namespace isc::datasrc::python;
using namespace isc::util::python;
@@ -181,6 +186,38 @@ initModulePart_ZoneIterator(PyObject* mod) {
}
bool
+initModulePart_ZoneLoader(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&zone_loader_type) < 0) {
+ return (false);
+ }
+ void* p = &zone_loader_type;
+ if (PyModule_AddObject(mod, "ZoneLoader", static_cast<PyObject*>(p)) < 0) {
+ return (false);
+ }
+ Py_INCREF(&zone_loader_type);
+
+ try {
+ installClassVariable(zone_loader_type, "PROGRESS_UNKNOWN",
+ Py_BuildValue("d", ZoneLoader::PROGRESS_UNKNOWN));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in ZoneLoader initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneLoader initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+bool
initModulePart_ZoneUpdater(PyObject* mod) {
// We initialize the static description object with PyType_Ready(),
// then add it to the module. This is not just a check! (leaving
@@ -234,8 +271,9 @@ initModulePart_ZoneJournalReader(PyObject* mod) {
}
PyObject* po_DataSourceError;
-PyObject* po_OutOfZone;
+PyObject* po_MasterFileError;
PyObject* po_NotImplemented;
+PyObject* po_OutOfZone;
PyModuleDef iscDataSrc = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
@@ -260,6 +298,26 @@ PyInit_datasrc(void) {
return (NULL);
}
+ try {
+ po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
+ NULL);
+ PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
+ po_MasterFileError = PyErr_NewException("isc.datasrc.MasterFileError",
+ po_DataSourceError, NULL);
+ PyObjectContainer(po_MasterFileError).
+ installToModule(mod, "MasterFileError");
+ po_OutOfZone = PyErr_NewException("isc.datasrc.OutOfZone", NULL, NULL);
+ PyObjectContainer(po_OutOfZone).installToModule(mod, "OutOfZone");
+ po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
+ NULL, NULL);
+ PyObjectContainer(po_NotImplemented).installToModule(mod,
+ "NotImplemented");
+
+ } catch (...) {
+ Py_DECREF(mod);
+ return (NULL);
+ }
+
if (!initModulePart_DataSourceClient(mod)) {
Py_DECREF(mod);
return (NULL);
@@ -290,17 +348,7 @@ PyInit_datasrc(void) {
return (NULL);
}
- try {
- po_DataSourceError = PyErr_NewException("isc.datasrc.Error", NULL,
- NULL);
- PyObjectContainer(po_DataSourceError).installToModule(mod, "Error");
- po_OutOfZone = PyErr_NewException("isc.datasrc.OutOfZone", NULL, NULL);
- PyObjectContainer(po_OutOfZone).installToModule(mod, "OutOfZone");
- po_NotImplemented = PyErr_NewException("isc.datasrc.NotImplemented",
- NULL, NULL);
- PyObjectContainer(po_NotImplemented).installToModule(mod,
- "NotImplemented");
- } catch (...) {
+ if (!initModulePart_ZoneLoader(mod)) {
Py_DECREF(mod);
return (NULL);
}
diff --git a/src/lib/python/isc/datasrc/master.py b/src/lib/python/isc/datasrc/master.py
deleted file mode 100644
index 2c22190..0000000
--- a/src/lib/python/isc/datasrc/master.py
+++ /dev/null
@@ -1,616 +0,0 @@
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-import sys, re, string
-import time
-import os
-#########################################################################
-# define exceptions
-#########################################################################
-class MasterFileError(Exception):
- pass
-
-#########################################################################
-# pop: remove the first word from a line
-# input: a line
-# returns: first word, rest of the line
-#########################################################################
-def pop(line):
- list = line.split()
- first, rest = '', ''
- if len(list) != 0:
- first = list[0]
- if len(list) > 1:
- rest = ' '.join(list[1:])
- return first, rest
-
-#########################################################################
-# cleanup: removes excess content from zone file data, including comments
-# and extra whitespace
-# input:
-# line of text
-# returns:
-# the same line, with comments removed, leading and trailing
-# whitespace removed, and all other whitespace compressed to
-# single spaces
-#########################################################################
-decomment = re.compile('^\s*((?:[^;"]|"[^"]*")*)\s*(?:|;.*)$')
-# Regular expression explained:
-# First, ignore any whitespace at the start. Then take the content,
-# each bit is either a harmless character (no ; nor ") or a string -
-# sequence between " " not containing double quotes. Then there may
-# be a comment at the end.
-def cleanup(s):
- global decomment
- s = s.strip().expandtabs()
- s = decomment.search(s).group(1)
- return ' '.join(s.split())
-
-#########################################################################
-# istype: check whether a string is a known RR type.
-# returns: boolean
-#########################################################################
-rrtypes = set(['a', 'aaaa', 'afsdb', 'apl', 'cert', 'cname', 'dhcid',
- 'dlv', 'dname', 'dnskey', 'ds', 'gpos', 'hinfo', 'hip',
- 'ipseckey', 'isdn', 'key', 'kx', 'loc', 'mb', 'md',
- 'mf', 'mg', 'minfo', 'mr', 'mx', 'naptr', 'ns', 'nsap',
- 'nsap-ptr', 'nsec', 'nsec3', 'nsec3param', 'null',
- 'nxt', 'opt', 'ptr', 'px', 'rp', 'rrsig', 'rt', 'sig',
- 'soa', 'spf', 'srv', 'sshfp', 'tkey', 'tsig', 'txt',
- 'x25', 'wks'])
-def istype(s):
- global rrtypes
- if s.lower() in rrtypes:
- return True
- else:
- return False
-
-#########################################################################
-# isclass: check whether a string is a known RR class. (only 'IN' is
-# supported, but the others must still be recognizable.)
-# returns: boolean
-#########################################################################
-rrclasses = set(['in', 'ch', 'chaos', 'hs', 'hesiod'])
-def isclass(s):
- global rrclasses
- if s.lower() in rrclasses:
- return True
- else:
- return False
-
-#########################################################################
-# isname: check whether a string is a valid DNS name.
-# returns: boolean
-#########################################################################
-name_regex = re.compile('[-\w\$\d\/*]+(?:\.[-\w\$\d\/]+)*\.?')
-def isname(s):
- global name_regex
- if s == '.' or name_regex.match(s):
- return True
- else:
- return False
-
-#########################################################################
-# isttl: check whether a string is a valid TTL specifier.
-# returns: boolean
-#########################################################################
-ttl_regex = re.compile('([0-9]+[wdhms]?)+$', re.I)
-def isttl(s):
- global ttl_regex
- if ttl_regex.match(s):
- return True
- else:
- return False
-
-#########################################################################
-# parse_ttl: convert a TTL field into an integer TTL value
-# (multiplying as needed for minutes, hours, etc.)
-# input:
-# string
-# returns:
-# int
-# throws:
-# MasterFileError
-#########################################################################
-def parse_ttl(s):
- sum = 0
- if not isttl(s):
- raise MasterFileError('Invalid TTL: ' + s)
- for ttl_expr in re.findall('\d+[wdhms]?', s, re.I):
- if ttl_expr.isdigit():
- ttl = int(ttl_expr)
- sum += ttl
- continue
- ttl = int(ttl_expr[:-1])
- suffix = ttl_expr[-1].lower()
- if suffix == 'w':
- ttl *= 604800
- elif suffix == 'd':
- ttl *= 86400
- elif suffix == 'h':
- ttl *= 3600
- elif suffix == 'm':
- ttl *= 60
- sum += ttl
- return str(sum)
-
-#########################################################################
-# records: generator function to return complete RRs from the zone file,
-# combining lines when necessary because of parentheses
-# input:
-# descriptor for a zone master file (returned from openzone)
-# yields:
-# complete RR
-#########################################################################
-def records(input):
- record = []
- complete = True
- paren = 0
- size = 0
- for line in input:
- size += len(line)
- list = cleanup(line).split()
- for word in list:
- if paren == 0:
- left, p, right = word.partition('(')
- if p == '(':
- if left: record.append(left)
- if right: record.append(right)
- paren += 1
- else:
- record.append(word)
- else:
- left, p, right = word.partition(')')
- if p == ')':
- if left: record.append(left)
- if right: record.append(right)
- paren -= 1
- else:
- record.append(word)
-
- if paren == 1 or not record:
- continue
-
- ret = ' '.join(record)
- record = []
- oldsize = size
- size = 0
- yield ret, oldsize
-
-#########################################################################
-# define the MasterFile class for reading zone master files
-#########################################################################
-class MasterFile:
- __rrclass = 'IN'
- __maxttl = 0x7fffffff
- __ttl = ''
- __lastttl = ''
- __zonefile = ''
- __name = ''
- __file_level = 0
- __file_type = ""
- __init_time = time.time()
- __records_num = 0
-
- def __init__(self, filename, initial_origin = '', verbose = False):
- self.__initial_origin = initial_origin
- self.__origin = initial_origin
- self.__datafile = filename
-
- try:
- self.__zonefile = open(filename, 'r')
- except:
- raise MasterFileError("Could not open " + filename)
- self.__filesize = os.fstat(self.__zonefile.fileno()).st_size
-
- self.__cur = 0
- self.__numback = 0
- self.__verbose = verbose
- try:
- self.__zonefile = open(filename, 'r')
- except:
- raise MasterFileError("Could not open " + filename)
-
- def __status(self):
- interval = time.time() - MasterFile.__init_time
- if self.__filesize == 0:
- percent = 100
- else:
- percent = (self.__cur * 100)/self.__filesize
-
- sys.stdout.write("\r" + (80 * " "))
- sys.stdout.write("\r%d RR(s) loaded in %.2f second(s) (%.2f%% of %s%s)"\
- % (MasterFile.__records_num, interval, percent, MasterFile.__file_type, self.__datafile))
-
- def __del__(self):
- if self.__zonefile:
- self.__zonefile.close()
- ########################################################################
- # check if the zonename is relative
- # no then return
- # yes , sets the relative domain name to the stated name
- #######################################################################
- def __statedname(self, name, record):
- if name[-1] != '.':
- if not self.__origin:
- raise MasterFileError("Cannot parse RR, No $ORIGIN: " + record)
- elif self.__origin == '.':
- name += '.'
- else:
- name += '.' + self.__origin
- return name
- #####################################################################
- # handle $ORIGIN, $TTL and $GENERATE directives
- # (currently only $ORIGIN and $TTL are implemented)
- # input:
- # a line from a zone file
- # returns:
- # a boolean indicating whether a directive was found
- # throws:
- # MasterFileError
- #########################################################################
- def __directive(self, s):
- first, more = pop(s)
- second, more = pop(more)
- if re.match('\$origin', first, re.I):
- if not second or not isname(second):
- raise MasterFileError('Invalid $ORIGIN')
- if more:
- raise MasterFileError('Invalid $ORIGIN')
- if second[-1] == '.':
- self.__origin = second
- elif not self.__origin:
- raise MasterFileError("$ORIGIN is not absolute in record: %s" % s)
- elif self.__origin != '.':
- self.__origin = second + '.' + self.__origin
- else:
- self.__origin = second + '.'
- return True
- elif re.match('\$ttl', first, re.I):
- if not second or not isttl(second):
- raise MasterFileError('Invalid TTL: "' + second + '"')
- if more:
- raise MasterFileError('Invalid $TTL statement')
- MasterFile.__ttl = parse_ttl(second)
- if int(MasterFile.__ttl) > self.__maxttl:
- raise MasterFileError('TTL too high: ' + second)
- return True
- elif re.match('\$generate', first, re.I):
- raise MasterFileError('$GENERATE not yet implemented')
- else:
- return False
-
- #########################################################################
- # handle $INCLUDE directives
- # input:
- # a line from a zone file
- # returns:
- # the parsed output of the included file, if any, or an empty array
- # throws:
- # MasterFileError
- #########################################################################
- __include_syntax1 = re.compile('\s+(\S+)(?:\s+(\S+))?$', re.I)
- __include_syntax2 = re.compile('\s+"([^"]+)"(?:\s+(\S+))?$', re.I)
- __include_syntax3 = re.compile("\s+'([^']+)'(?:\s+(\S+))?$", re.I)
- def __include(self, s):
- if not s.lower().startswith('$include'):
- return "", ""
- s = s[len('$include'):]
- m = self.__include_syntax1.match(s)
- if not m:
- m = self.__include_syntax2.match(s)
- if not m:
- m = self.__include_syntax3.match(s)
- if not m:
- raise MasterFileError('Invalid $include format')
- file = m.group(1)
- if m.group(2):
- if not isname(m.group(2)):
- raise MasterFileError('Invalid $include format (invalid origin)')
- origin = self.__statedname(m.group(2), s)
- else:
- origin = self.__origin
- return file, origin
-
- #########################################################################
- # try parsing an RR on the assumption that the type is specified in
- # field 4, and name, ttl and class are in fields 1-3
- # are all specified, with type in field 4
- # input:
- # a record to parse, and the most recent name found in prior records
- # returns:
- # empty list if parse failed, else name, ttl, class, type, rdata
- #########################################################################
- def __four(self, record, curname):
- ret = ''
- list = record.split()
- if len(list) <= 4:
- return ret
- if istype(list[3]):
- if isclass(list[2]) and isttl(list[1]) and isname(list[0]):
- name, ttl, rrclass, rrtype = list[0:4]
- ttl = parse_ttl(ttl)
- MasterFile.__lastttl = ttl or MasterFile.__lastttl
- rdata = ' '.join(list[4:])
- ret = name, ttl, rrclass, rrtype, rdata
- elif isclass(list[1]) and isttl(list[2]) and isname(list[0]):
- name, rrclass, ttl, rrtype = list[0:4]
- ttl = parse_ttl(ttl)
- MasterFile.__lastttl = ttl or MasterFile.__lastttl
- rdata = ' '.join(list[4:])
- ret = name, ttl, rrclass, rrtype, rdata
- return ret
-
- #########################################################################
- # try parsing an RR on the assumption that the type is specified
- # in field 3, and one of name, ttl, or class has been omitted
- # input:
- # a record to parse, and the most recent name found in prior records
- # returns:
- # empty list if parse failed, else name, ttl, class, type, rdata
- #########################################################################
- def __getttl(self):
- return MasterFile.__ttl or MasterFile.__lastttl
-
- def __three(self, record, curname):
- ret = ''
- list = record.split()
- if len(list) <= 3:
- return ret
- if istype(list[2]) and not istype(list[1]):
- if isclass(list[1]) and not isttl(list[0]) and isname(list[0]):
- rrclass = list[1]
- ttl = self.__getttl()
- name = list[0]
- elif not isclass(list[1]) and isttl(list[1]) and not isclass(list[0]) and isname(list[0]):
- rrclass = self.__rrclass
- ttl = parse_ttl(list[1])
- MasterFile.__lastttl = ttl or MasterFile.__lastttl
- name = list[0]
- elif curname and isclass(list[1]) and isttl(list[0]):
- rrclass = list[1]
- ttl = parse_ttl(list[0])
- MasterFile.__lastttl = ttl or MasterFile.__lastttl
- name = curname
- elif curname and isttl(list[1]) and isclass(list[0]):
- rrclass = list[0]
- ttl = parse_ttl(list[1])
- MasterFile.__lastttl = ttl or MasterFile.__lastttl
- name = curname
- else:
- return ret
- rrtype = list[2]
- rdata = ' '.join(list[3:])
- ret = name, ttl, rrclass, rrtype, rdata
- return ret
-
- #########################################################################
- # try parsing an RR on the assumption that the type is specified in
- # field 2, and field 1 is either name or ttl
- # input:
- # a record to parse, and the most recent name found in prior records
- # returns:
- # empty list if parse failed, else name, ttl, class, type, rdata
- # throws:
- # MasterFileError
- #########################################################################
- def __two(self, record, curname):
- ret = ''
- list = record.split()
- if len(list) <= 2:
- return ret
- if istype(list[1]):
- rrclass = self.__rrclass
- rrtype = list[1]
- if list[0].lower() == 'rrsig':
- name = curname
- ttl = self.__getttl()
- rrtype = list[0]
- rdata = ' '.join(list[1:])
- elif isttl(list[0]):
- ttl = parse_ttl(list[0])
- name = curname
- rdata = ' '.join(list[2:])
- elif isclass(list[0]):
- ttl = self.__getttl()
- name = curname
- rdata = ' '.join(list[2:])
- elif isname(list[0]):
- name = list[0]
- ttl = self.__getttl()
- rdata = ' '.join(list[2:])
- else:
- raise MasterFileError("Cannot parse RR: " + record)
-
- ret = name, ttl, rrclass, rrtype, rdata
- return ret
-
- ########################################################################
- #close verbose
- ######################################################################
- def closeverbose(self):
- self.__status()
-
- #########################################################################
- # zonedata: generator function to parse a zone master file and return
- # each RR as a (name, ttl, type, class, rdata) tuple
- #########################################################################
- def zonedata(self):
- name = ''
- last_status = 0.0
- flag = 1
-
- for record, size in records(self.__zonefile):
- if self.__verbose:
- now = time.time()
- if flag == 1:
- self.__status()
- flag = 0
- if now - last_status >= 1.0:
- self.__status()
- last_status = now
-
- self.__cur += size
- if self.__directive(record):
- continue
-
- incl, suborigin = self.__include(record)
- if incl:
- if self.__filesize == 0:
- percent = 100
- else:
- percent = (self.__cur * 100)/self.__filesize
- if self.__verbose:
- sys.stdout.write("\r" + (80 * " "))
- sys.stdout.write("\rIncluding \"%s\" from \"%s\"\n" % (incl, self.__datafile))
- MasterFile.__file_level += 1
- MasterFile.__file_type = "included "
- sub = MasterFile(incl, suborigin, self.__verbose)
-
- for rrname, ttl, rrclass, rrtype, rdata in sub.zonedata():
- yield (rrname, ttl, rrclass, rrtype, rdata)
- if self.__verbose:
- sub.closeverbose()
- MasterFile.__file_level -= 1
- if MasterFile.__file_level == 0:
- MasterFile.__file_type = ""
- del sub
- continue
-
- # replace @ with origin
- rl = record.split()
- if rl[0] == '@':
- rl[0] = self.__origin
- if not self.__origin:
- raise MasterFileError("Cannot parse RR, No $ORIGIN: " + record)
- record = ' '.join(rl)
-
- result = self.__four(record, name)
-
- if not result:
- result = self.__three(record, name)
-
- if not result:
- result = self.__two(record, name)
-
- if not result:
- first, rdata = pop(record)
- if istype(first):
- result = name, self.__getttl(), self.__rrclass, first, rdata
-
- if not result:
- raise MasterFileError("Cannot parse RR: " + record)
-
- name, ttl, rrclass, rrtype, rdata = result
- name = self.__statedname(name, record)
-
- if rrclass.lower() != 'in':
- raise MasterFileError("CH and HS zones not supported")
-
- # add origin to rdata containing names, if necessary
- if rrtype.lower() in ('cname', 'dname', 'ns', 'ptr'):
- if not isname(rdata):
- raise MasterFileError("Invalid " + rrtype + ": " + rdata)
- rdata = self.__statedname(rdata, record)
-
- if rrtype.lower() == 'soa':
- soa = rdata.split()
- if len(soa) < 2 or not isname(soa[0]) or not isname(soa[1]):
- raise MasterFileError("Invalid " + rrtype + ": " + rdata)
- soa[0] = self.__statedname(soa[0], record)
- soa[1] = self.__statedname(soa[1], record)
- if not MasterFile.__ttl and not ttl:
- MasterFile.__ttl = MasterFile.__ttl or parse_ttl(soa[-1])
- ttl = MasterFile.__ttl
-
- for index in range(3, len(soa)):
- if isttl(soa[index]):
- soa[index] = parse_ttl(soa[index])
- else :
- raise MasterFileError("No TTL specified; in soa record!")
- rdata = ' '.join(soa)
-
- if not ttl:
- raise MasterFileError("No TTL specified; zone rejected")
-
- if rrtype.lower() == 'mx':
- mx = rdata.split()
- if len(mx) != 2 or not isname(mx[1]):
- raise MasterFileError("Invalid " + rrtype + ": " + rdata)
- if mx[1][-1] != '.':
- mx[1] += '.' + self.__origin
- rdata = ' '.join(mx)
- MasterFile.__records_num += 1
- yield (name, ttl, rrclass, rrtype, rdata)
-
- #########################################################################
- # zonename: scans zone data for an SOA record, returns its name, restores
- # the zone file to its prior state
- #########################################################################
- def zonename(self):
- if self.__name:
- return self.__name
- old_origin = self.__origin
- self.__origin = self.__initial_origin
- cur_value = self.__cur
- old_location = self.__zonefile.tell()
- old_verbose = self.__verbose
- self.__verbose = False
- self.__zonefile.seek(0)
-
- for name, ttl, rrclass, rrtype, rdata in self.zonedata():
- if rrtype.lower() == 'soa':
- break
- self.__zonefile.seek(old_location)
- self.__origin = old_origin
- self.__cur = cur_value
- if rrtype.lower() != 'soa':
- raise MasterFileError("No SOA found")
- self.__name = name
- self.__verbose = old_verbose
- return name
-
- #########################################################################
- # reset: reset the state of the master file
- #########################################################################
- def reset(self):
- self.__zonefile.seek(0)
- self.__origin = self.__initial_origin
- MasterFile.__ttl = ''
- MasterFile.__lastttl = ''
-
-#########################################################################
-# main: used for testing; parse a zone file and print out each record
-# broken up into separate name, ttl, class, type, and rdata files
-#########################################################################
-def main():
- try:
- file = sys.argv[1]
- except:
- file = 'testfile'
- master = MasterFile(file, '.')
- print ('zone name: ' + master.zonename())
- print ('---------------------')
- for name, ttl, rrclass, rrtype, rdata in master.zonedata():
- print ('name: ' + name)
- print ('ttl: ' + ttl)
- print ('rrclass: ' + rrclass)
- print ('rrtype: ' + rrtype)
- print ('rdata: ' + rdata)
- print ('---------------------')
- del master
-
-if __name__ == "__main__":
- main()
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
index 58ea8cd..0d94a86 100644
--- a/src/lib/python/isc/datasrc/tests/.gitignore
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -1 +1,2 @@
/*.sqlite3.copied
+/zoneloadertest.sqlite3
diff --git a/src/lib/python/isc/datasrc/tests/Makefile.am b/src/lib/python/isc/datasrc/tests/Makefile.am
index 34af092..c16d295 100644
--- a/src/lib/python/isc/datasrc/tests/Makefile.am
+++ b/src/lib/python/isc/datasrc/tests/Makefile.am
@@ -1,15 +1,18 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
-# old tests, TODO remove or change to use new API?
-#PYTESTS = master_test.py
-PYTESTS = datasrc_test.py sqlite3_ds_test.py clientlist_test.py
+PYTESTS = datasrc_test.py sqlite3_ds_test.py
+PYTESTS += clientlist_test.py zone_loader_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testdata/brokendb.sqlite3
EXTRA_DIST += testdata/example.com.sqlite3
+EXTRA_DIST += testdata/example.com.source.sqlite3
EXTRA_DIST += testdata/newschema.sqlite3
EXTRA_DIST += testdata/oldschema.sqlite3
EXTRA_DIST += testdata/new_minor_schema.sqlite3
+EXTRA_DIST += testdata/example.com
+EXTRA_DIST += testdata/example.com.ch
CLEANFILES = $(abs_builddir)/rwtest.sqlite3.copied
+CLEANFILES += $(abs_builddir)/zoneloadertest.sqlite3
# If necessary (rare cases), explicitly specify paths to dynamic libraries
# required by loadable python modules.
@@ -24,6 +27,7 @@ LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/data
endif
# test using command-line arguments, so use check-local target instead of TESTS
+# We need to define B10_FROM_BUILD for datasrc loadable modules
check-local:
if ENABLE_PYTHON_COVERAGE
touch $(abs_top_srcdir)/.coverage
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index b8e6fb2..659e7a8 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -200,7 +200,7 @@ class DataSrcClient(unittest.TestCase):
])
# For RRSIGS, we can't add the fake data through the API, so we
# simply pass no rdata at all (which is skipped by the check later)
-
+
# Since we passed separate_rrs = True to get_iterator, we get several
# sets of RRSIGs, one for each TTL
add_rrset(expected_rrset_list, name, rrclass,
@@ -634,6 +634,66 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(None, iterator.get_soa())
self.assertEqual(None, iterator.get_next_rrset())
+ def test_create_or_delete_zone_args(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+
+ for method in [dsc.create_zone, dsc.delete_zone]:
+ self.assertRaises(TypeError, method)
+ self.assertRaises(TypeError, method, 1)
+ self.assertRaises(TypeError, method, None)
+ self.assertRaises(TypeError, method, "foo.")
+ self.assertRaises(TypeError, method, isc.dns.Name("example.org"),
+ 1)
+
+ def test_create_delete_zone(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+ # Note, using example.org here, which should not exist
+ zone_name = isc.dns.Name("example.org")
+ self.assertIsNone(dsc.get_updater(zone_name, True))
+ self.assertRaises(isc.datasrc.Error, dsc.get_iterator, zone_name)
+
+ # delete_zone should return False, meaning it didn't exist.
+ self.assertFalse(dsc.delete_zone(zone_name))
+
+ self.assertTrue(dsc.create_zone(zone_name))
+
+ # should exist now, we should be able to get an updater
+ # and currently it should be empty
+ self.assertIsNotNone(dsc.get_updater(zone_name, True))
+ iterator = dsc.get_iterator(zone_name)
+ self.assertEqual(None, iterator.get_soa())
+ self.assertEqual(None, iterator.get_next_rrset())
+
+ # Trying to create it again should return False
+ self.assertFalse(dsc.create_zone(zone_name))
+
+ # Now that it exists, delete_zone should return True, and it cannot
+ # be found any more.
+ self.assertTrue(dsc.delete_zone(zone_name))
+ self.assertEqual(isc.datasrc.DataSourceClient.NOTFOUND,
+ dsc.find_zone(zone_name)[0])
+
+ def test_create_zone_locked(self):
+ zone_name = isc.dns.Name("example.org")
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+ updater = dsc.get_updater(isc.dns.Name("example.com"), True)
+
+ # Should fail since db is locked
+ self.assertRaises(isc.datasrc.Error, dsc.create_zone, zone_name)
+ self.assertRaises(isc.datasrc.Error, dsc.delete_zone,
+ isc.dns.Name('example.com'))
+
+ # Cancel updater, then create/delete should succeed
+ updater = None
+ self.assertTrue(dsc.create_zone(zone_name))
+ self.assertTrue(dsc.delete_zone(zone_name))
+
+ def test_create_zone_not_implemented(self):
+ mem_cfg = '{ "type": "memory", "class": "IN", "zones": [] }';
+ dsc = isc.datasrc.DataSourceClient("memory", mem_cfg)
+ self.assertRaises(isc.datasrc.NotImplemented, dsc.create_zone,
+ isc.dns.Name("example.com"))
+
class JournalWrite(unittest.TestCase):
def setUp(self):
# Make a fresh copy of the writable database with all original content
diff --git a/src/lib/python/isc/datasrc/tests/master_test.py b/src/lib/python/isc/datasrc/tests/master_test.py
deleted file mode 100644
index c65858e..0000000
--- a/src/lib/python/isc/datasrc/tests/master_test.py
+++ /dev/null
@@ -1,35 +0,0 @@
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-from isc.datasrc.master import *
-import unittest
-
-class TestTTL(unittest.TestCase):
- def test_ttl(self):
- self.assertTrue(isttl('3600'))
- self.assertTrue(isttl('1W'))
- self.assertTrue(isttl('1w'))
- self.assertTrue(isttl('2D'))
- self.assertTrue(isttl('2d'))
- self.assertTrue(isttl('30M'))
- self.assertTrue(isttl('30m'))
- self.assertTrue(isttl('10S'))
- self.assertTrue(isttl('10s'))
- self.assertTrue(isttl('2W1D'))
- self.assertFalse(isttl('not a ttl'))
- self.assertFalse(isttl('1X'))
-
-if __name__ == '__main__':
- unittest.main()
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com b/src/lib/python/isc/datasrc/tests/testdata/example.com
new file mode 100644
index 0000000..24e22e1
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/example.com
@@ -0,0 +1,8 @@
+example.com. 1000 IN SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com. 1000 IN NS a.dns.example.com.
+example.com. 1000 IN NS b.dns.example.com.
+example.com. 1000 IN NS c.dns.example.com.
+a.dns.example.com. 1000 IN A 1.1.1.1
+b.dns.example.com. 1000 IN A 3.3.3.3
+b.dns.example.com. 1000 IN AAAA 4:4::4:4
+b.dns.example.com. 1000 IN AAAA 5:5::5:5
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com.ch b/src/lib/python/isc/datasrc/tests/testdata/example.com.ch
new file mode 100644
index 0000000..95c0e9a
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/testdata/example.com.ch
@@ -0,0 +1,8 @@
+example.com. 1000 CH SOA a.dns.example.com. mail.example.com. 1 1 1 1 1
+example.com. 1000 CH NS a.dns.example.com.
+example.com. 1000 CH NS b.dns.example.com.
+example.com. 1000 CH NS c.dns.example.com.
+a.dns.example.com. 1000 CH A 1.1.1.1
+b.dns.example.com. 1000 CH A 3.3.3.3
+b.dns.example.com. 1000 CH AAAA 4:4::4:4
+b.dns.example.com. 1000 CH AAAA 5:5::5:5
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com.source.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/example.com.source.sqlite3
new file mode 100644
index 0000000..d4de6da
Binary files /dev/null and b/src/lib/python/isc/datasrc/tests/testdata/example.com.source.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3
index 9c71cb5..8ac72f4 100644
Binary files a/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 and b/src/lib/python/isc/datasrc/tests/testdata/example.com.sqlite3 differ
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
new file mode 100644
index 0000000..62f67cd
--- /dev/null
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -0,0 +1,251 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import isc.datasrc
+import isc.dns
+
+import os
+import unittest
+import shutil
+import sys
+
+# Constants and common data used in tests
+
+TESTDATA_PATH = os.environ['TESTDATA_PATH']
+TESTDATA_WRITE_PATH = os.environ['TESTDATA_WRITE_PATH']
+
+ZONE_FILE = TESTDATA_PATH + '/example.com'
+STATIC_ZONE_FILE = '../../../../datasrc/static.zone'
+SOURCE_DB_FILE = TESTDATA_PATH + '/example.com.source.sqlite3'
+ORIG_DB_FILE = TESTDATA_PATH + '/example.com.sqlite3'
+DB_FILE = TESTDATA_WRITE_PATH + '/zoneloadertest.sqlite3'
+DB_CLIENT_CONFIG = '{ "database_file": "' + DB_FILE + '" }'
+DB_SOURCE_CLIENT_CONFIG = '{ "database_file": "' + SOURCE_DB_FILE + '" }'
+
+ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
+ 'admin.example.com. 1234 3600 1800 2419200 7200\n'
+NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
+ 'mail.example.com. 1 1 1 1 1\n'
+PROGRESS_UNKNOWN = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
+
+class ZoneLoaderTests(unittest.TestCase):
+ def setUp(self):
+ self.test_name = isc.dns.Name("example.com")
+ self.test_file = ZONE_FILE
+ self.client = isc.datasrc.DataSourceClient("sqlite3", DB_CLIENT_CONFIG)
+ # Make a fresh copy of the database
+ shutil.copyfile(ORIG_DB_FILE, DB_FILE)
+ # Some tests set source client; if so, check refcount in
+ # tearDown, since most tests don't, set it to None by default.
+ self.source_client = None
+ self.loader = None
+ self.assertEqual(2, sys.getrefcount(self.test_name))
+ self.assertEqual(2, sys.getrefcount(self.client))
+
+ def tearDown(self):
+ # We can only create 1 loader at a time (it locks the db), and it
+ # may not be destroyed immediately if there is an exception in a
+ # test. So the tests that do create one should put it in self, and
+ # we make sure to invalidate it here.
+
+ # We can also use this to check reference counts; if a loader
+ # exists, the client and source client (if any) should have
+ # an increased reference count (but the name should not, this
+ # is only used in the initializer)
+ if self.loader is not None:
+ self.assertEqual(2, sys.getrefcount(self.test_name))
+ self.assertEqual(3, sys.getrefcount(self.client))
+ if self.source_client is not None:
+ self.assertEqual(3, sys.getrefcount(self.source_client))
+ self.loader = None
+
+ # Now that the loader has been destroyed, the refcounts
+ # of its arguments should be back to their originals
+ self.assertEqual(2, sys.getrefcount(self.test_name))
+ self.assertEqual(2, sys.getrefcount(self.client))
+ if self.source_client is not None:
+ self.assertEqual(2, sys.getrefcount(self.source_client))
+
+ def test_bad_constructor(self):
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader)
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader, 1)
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+ None, self.test_name, self.test_file)
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+ self.client, None, self.test_file)
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+ self.client, self.test_name, None)
+ self.assertRaises(TypeError, isc.datasrc.ZoneLoader,
+ self.client, self.test_name, self.test_file, 1)
+
+ def check_zone_soa(self, soa_txt):
+ """
+ Check that the given SOA RR exists and matches the expected string
+ """
+ result, finder = self.client.find_zone(self.test_name)
+ self.assertEqual(self.client.SUCCESS, result)
+ result, rrset, _ = finder.find(self.test_name, isc.dns.RRType.SOA())
+ self.assertEqual(finder.SUCCESS, result)
+ self.assertEqual(soa_txt, rrset.to_text())
+
+ def check_load(self):
+ self.check_zone_soa(ORIG_SOA_TXT)
+ self.loader.load()
+ self.check_zone_soa(NEW_SOA_TXT)
+
+ # And after that, it should throw
+ self.assertRaises(isc.dns.InvalidOperation, self.loader.load)
+
+ def test_load_from_file(self):
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ self.test_file)
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(0, self.loader.get_progress())
+
+ self.check_load()
+
+ # Expected values are hardcoded, taken from the test zone file,
+ # assuming it won't change too often. progress should reach 100% (=1).
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(1, self.loader.get_progress())
+
+ def test_load_from_client(self):
+ self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+ DB_SOURCE_CLIENT_CONFIG)
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ self.source_client)
+
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
+ self.check_load()
+
+ # In case of loading from another data source, progress is unknown.
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
+ def check_load_incremental(self, from_file=True):
+ # New zone has 8 RRs
+ # After 5, it should return False
+ self.assertFalse(self.loader.load_incremental(5))
+ # New zone should not have been loaded yet
+ self.check_zone_soa(ORIG_SOA_TXT)
+
+ # In case it's from a zone file, get_progress should be in the middle
+ # of (0, 1). expected value is taken from the test zone file
+ # (total size = 422, current position = 288)
+ if from_file:
+ # To avoid any false positive due to rounding errors, we convert
+ # them to near integers between 0 and 100.
+ self.assertEqual(int((288 * 100) / 422),
+ int(self.loader.get_progress() * 100))
+ # Also check the return value has higher precision.
+ self.assertNotEqual(int(288 * 100 / 422),
+ 100 * self.loader.get_progress())
+
+ # After 5 more, it should return True (only having read 3)
+ self.assertTrue(self.loader.load_incremental(5))
+ # New zone should now be loaded
+ self.check_zone_soa(NEW_SOA_TXT)
+
+ # And after that, it should throw
+ self.assertRaises(isc.dns.InvalidOperation,
+ self.loader.load_incremental, 5)
+
+ def test_load_from_file_incremental(self):
+ # Create loader and load the zone
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ self.test_file)
+ self.check_load_incremental()
+
+ def test_load_from_client_incremental(self):
+ self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+ DB_SOURCE_CLIENT_CONFIG)
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ self.source_client)
+ self.check_load_incremental(False)
+
+ def test_bad_file(self):
+ self.check_zone_soa(ORIG_SOA_TXT)
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ 'no such file')
+ self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
+ self.check_zone_soa(ORIG_SOA_TXT)
+
+ def test_bad_file_incremental(self):
+ self.check_zone_soa(ORIG_SOA_TXT)
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ 'no such file')
+ self.assertRaises(isc.datasrc.MasterFileError,
+ self.loader.load_incremental, 1)
+ self.check_zone_soa(ORIG_SOA_TXT)
+
+ def test_no_such_zone_in_target(self):
+ self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
+ self.client, isc.dns.Name("unknownzone"),
+ self.test_file)
+
+ def test_no_such_zone_in_source(self):
+ # Reuse a zone that exists in target but not in source
+ zone_name = isc.dns.Name("sql1.example.com")
+ self.source_client = isc.datasrc.DataSourceClient('sqlite3',
+ DB_SOURCE_CLIENT_CONFIG)
+
+ # make sure the zone exists in the target
+ found, _ = self.client.find_zone(zone_name)
+ self.assertEqual(self.client.SUCCESS, found)
+ # And that it does not in the source
+ found, _ = self.source_client.find_zone(zone_name)
+ self.assertNotEqual(self.source_client.SUCCESS, found)
+
+ self.assertRaises(isc.datasrc.Error, isc.datasrc.ZoneLoader,
+ self.client, zone_name, self.source_client)
+
+ def test_no_ds_load_support(self):
+ # This may change in the future, but atm, the in-mem ds does
+ # not support the API the zone loader uses (it has direct load calls)
+ inmem_client = isc.datasrc.DataSourceClient('memory',
+ '{ "type": "memory" }');
+ self.assertRaises(isc.datasrc.NotImplemented,
+ isc.datasrc.ZoneLoader,
+ inmem_client, self.test_name, self.test_file)
+
+ def test_wrong_class_from_file(self):
+ # If the file has wrong class, it is not detected until load time
+ self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
+ self.test_file + '.ch')
+ self.assertRaises(isc.datasrc.MasterFileError, self.loader.load)
+
+ def test_wrong_class_from_client(self):
+ # For ds->ds loading, wrong class is detected upon construction
+ # Need a bit of the extended setup for CH source client
+ clientlist = isc.datasrc.ConfigurableClientList(isc.dns.RRClass.CH())
+ clientlist.configure('[ { "type": "static", "params": "' +
+ STATIC_ZONE_FILE +'" } ]', False)
+ self.source_client, _, _ = clientlist.find(isc.dns.Name("bind."),
+ False, False)
+ self.assertRaises(isc.dns.InvalidParameter, isc.datasrc.ZoneLoader,
+ self.client, isc.dns.Name("bind."),
+ self.source_client)
+
+ def test_exception(self):
+ # Just check if masterfileerror is subclass of datasrc.Error
+ self.assertTrue(issubclass(isc.datasrc.MasterFileError,
+ isc.datasrc.Error))
+
+if __name__ == "__main__":
+ isc.log.init("bind10")
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/lib/python/isc/datasrc/zone_loader_inc.cc b/src/lib/python/isc/datasrc/zone_loader_inc.cc
new file mode 100644
index 0000000..ae6fa3c
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zone_loader_inc.cc
@@ -0,0 +1,176 @@
+namespace {
+const char* const ZoneLoader_doc = "\
+Class to load data into a data source client.\n\
+\n\
+This is a small wrapper class that is able to load data into a data\n\
+source. It can load either from another data source or from a master\n\
+file. The purpose of the class is only to hold the state for\n\
+incremental loading.\n\
+\n\
+The old content of zone is discarded and no journal is stored.\n\
+\n\
+ZoneLoader(destination, zone_name, master_file)\n\
+\n\
+ Constructor from master file.\n\
+\n\
+ This initializes the zone loader to load from a master file.\n\
+\n\
+ Exceptions:\n\
+ DataSourceError in case the zone does not exist in destination.\n\
+ This class does not support creating brand new zones,\n\
+ only loading data into them. In case a new zone is\n\
+ needed, it must be created beforehand.\n\
+ DataSourceError in case of other (possibly low-level) errors,\n\
+ such as read-only data source or database error.\n\
+\n\
+ Parameters:\n\
+ destination (isc.datasrc.DataSourceClient) The data source into\n\
+ which the loaded data should go.\n\
+ zone_name (isc.dns.Name) The origin of the zone. The class is\n\
+ implicit in the destination.\n\
+ master_file (string) Path to the master file to read data from.\n\
+\n\
+ZoneLoader(destination, zone_name, source)\n\
+\n\
+ Constructor from another data source.\n\
+\n\
+ Parameters:\n\
+ destination (isc.datasrc.DataSourceClient) The data source into\n\
+ which the loaded data should go.\n\
+ zone_name (isc.dns.Name) The origin of the zone. The class is\n\
+ implicit in the destination.\n\
+ source (isc.datasrc.DataSourceClient) The data source from\n\
+ which the data would be read.\n\
+\n\
+ Exceptions:\n\
+ InvalidParameter in case the class of destination and source\n\
+ differs.\n\
+ NotImplemented in case the source data source client doesn't\n\
+ provide an iterator.\n\
+ DataSourceError in case the zone does not exist in destination.\n\
+ This class does not support creating brand new zones,\n\
+ only loading data into them. In case a new zone is\n\
+ needed, it must be created beforehand.\n\
+ DataSourceError in case the zone does not exist in the source.\n\
+ DataSourceError in case of other (possibly low-level) errors,\n\
+ such as read-only data source or database error.\n\
+\n\
+ Parameters:\n\
+ destination The data source into which the loaded data should\n\
+ go.\n\
+ zone_name The origin of the zone.\n\
+ source The data source from which the data would be read.\n\
+\n\
+";
+
+const char* const ZoneLoader_load_doc = "\
+load() -> None\n\
+\n\
+Perform the whole load.\n\
+\n\
+This performs the whole loading operation. It may take a long time.\n\
+\n\
+Exceptions:\n\
+ InvalidOperation in case the loading was already completed before\n\
+ this call.\n\
+ DataSourceError in case some error (possibly low-level) happens.\n\
+ MasterFileError when the master_file is badly formatted or some\n\
+ similar problem is found when loading the master file.\n\
+\n\
+";
+
+const char* const ZoneLoader_loadIncremental_doc = "\
+load_incremental(limit) -> bool\n\
+\n\
+Load up to limit RRs.\n\
+\n\
+This performs a part of the loading. In case there's enough data in\n\
+the source, it copies limit RRs. It can copy less RRs during the final\n\
+call (when there's less than limit left).\n\
+\n\
+This can be called repeatedly until the whole zone is loaded, having\n\
+pauses in the loading for some purposes (for example reporting\n\
+progress).\n\
+\n\
+Exceptions:\n\
+ InvalidOperation in case the loading was already completed before\n\
+ this call (by load() or by a load_incremental that\n\
+ returned true).\n\
+ DataSourceError in case some error (possibly low-level) happens.\n\
+ MasterFileError when the master_file is badly formatted or some\n\
+ similar problem is found when loading the master file.\n\
+\n\
+Parameters:\n\
+ limit (integer) The maximum allowed number of RRs to be\n\
+ loaded during this call.\n\
+\n\
+Return Value(s): True in case the loading is completed, false if\n\
+there's more to load.\n\
+\n\
+Note that if the limit is exactly the number of RRs available to be\n\
+loaded, the method will still return False, and True will be returned\n\
+on the next call (which will load 0 RRs). This is because the end of\n\
+iterator or master file is detected when reading past the end, not\n\
+when the last one is read.\n\
+";
+
+const char* const ZoneLoader_getRRCount_doc = "\
+get_rr_count() -> integer\n\
+\n\
+Return the number of RRs loaded.\n\
+\n\
+This method returns the number of RRs loaded via this loader by the\n\
+time of the call. Before starting the load it will return 0. It will\n\
+return the total number of RRs of the zone on and after completing the\n\
+load.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
+
+// Modifications
+// - double => float
+const char* const ZoneLoader_getProgress_doc = "\
+get_progress() -> float\n\
+\n\
+Return the current progress of the loader.\n\
+\n\
+This method returns the current estimated progress of loader as a\n\
+value between 0 and 1 (inclusive); it's 0 before starting the load,\n\
+and 1 at the completion, and a value between these (exclusive) in the\n\
+middle of loading. It's an implementation detail how to calculate the\n\
+progress, which may vary depending on how the loader is constructed\n\
+and may even be impossible to detect effectively.\n\
+\n\
+If the progress cannot be determined, this method returns a special\n\
+value of PROGRESS_UNKNOWN, which is not included in the range between\n\
+0 and 1.\n\
+\n\
+As such, the application should use the return value only for\n\
+informational purposes such as logging. For example, it shouldn't be\n\
+used to determine whether loading is completed by comparing it to 1.\n\
+It should also expect the possibility of getting PROGRESS_UNKNOWN at\n\
+any call to this method; it shouldn't assume the specific way of\n\
+internal implementation as described below (which is provided for\n\
+informational purposes only).\n\
+\n\
+In this implementation, if the loader is constructed with a file name,\n\
+the progress value is measured by the number of characters read from\n\
+the zone file divided by the size of the zone file (with taking into\n\
+account any included files). Note that due to the possibility of\n\
+intermediate included files, the total file size cannot be fully fixed\n\
+until the completion of the load. And, due to this possibility, return\n\
+values from this method may not always increase monotonically.\n\
+\n\
+If it's constructed with another data source client, this method\n\
+always returns PROGRESS_UNKNOWN; in future, however, it may become\n\
+possible to return something more useful, e.g, based on the result of\n\
+get_rr_count() and the total number of RRs if the underlying data\n\
+source can provide the latter value efficiently.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zone_loader_python.cc b/src/lib/python/isc/datasrc/zone_loader_python.cc
new file mode 100644
index 0000000..c1915cc
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zone_loader_python.cc
@@ -0,0 +1,278 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <datasrc/zone_loader.h>
+#include <dns/python/name_python.h>
+#include <dns/python/pydnspp_common.h>
+#include <exceptions/exceptions.h>
+
+#include "client_python.h"
+#include "datasrc.h"
+#include "zone_loader_inc.cc"
+
+using namespace std;
+using namespace isc::dns::python;
+using namespace isc::datasrc;
+using namespace isc::datasrc::python;
+using namespace isc::util::python;
+
+namespace {
+// The s_* Class simply covers one instantiation of the object
+class s_ZoneLoader : public PyObject {
+public:
+ s_ZoneLoader() : cppobj(NULL), target_client(NULL), source_client(NULL)
+ {};
+ ZoneLoader* cppobj;
+ // a zoneloader should not survive its associated client(s),
+ // so add a ref to it at init
+ PyObject* target_client;
+ PyObject* source_client;
+};
+
+// General creation and destruction
+int
+ZoneLoader_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ PyObject *po_target_client = NULL;
+ PyObject *po_source_client = NULL;
+ PyObject *po_name = NULL;
+ char* master_file;
+ if (!PyArg_ParseTuple(args, "O!O!s", &datasourceclient_type,
+ &po_target_client, &name_type, &po_name,
+ &master_file) &&
+ !PyArg_ParseTuple(args, "O!O!O!", &datasourceclient_type,
+ &po_target_client, &name_type, &po_name,
+ &datasourceclient_type, &po_source_client)
+ ) {
+ PyErr_SetString(PyExc_TypeError,
+ "Invalid arguments to ZoneLoader constructor, "
+ "expects isc.datasrc.DataSourceClient, isc.dns.Name, "
+ "and either a string or another DataSourceClient");
+ return (-1);
+ }
+ PyErr_Clear();
+ try {
+ // The associated objects must be alive during the lifetime
+ // of this instance, so incref them (through a container in case
+ // of exceptions in this method)
+ Py_INCREF(po_target_client);
+ PyObjectContainer target_client(po_target_client);
+ if (po_source_client != NULL) {
+ // See above
+ Py_INCREF(po_source_client);
+ PyObjectContainer source_client(po_source_client);
+ self->cppobj = new ZoneLoader(
+ PyDataSourceClient_ToDataSourceClient(po_target_client),
+ PyName_ToName(po_name),
+ PyDataSourceClient_ToDataSourceClient(po_source_client));
+ self->source_client = source_client.release();
+ } else {
+ self->cppobj = new ZoneLoader(
+ PyDataSourceClient_ToDataSourceClient(po_target_client),
+ PyName_ToName(po_name),
+ master_file);
+ }
+ self->target_client = target_client.release();
+ return (0);
+ } catch (const isc::InvalidParameter& ivp) {
+ PyErr_SetString(po_InvalidParameter, ivp.what());
+ } catch (const isc::datasrc::DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ } catch (const isc::NotImplemented& ni) {
+ PyErr_SetString(getDataSourceException("NotImplemented"), ni.what());
+ } catch (const std::exception& stde) {
+ PyErr_SetString(getDataSourceException("Error"), stde.what());
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ }
+ return (-1);
+}
+
+void
+ZoneLoader_destroy(PyObject* po_self) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ if (self->target_client != NULL) {
+ Py_DECREF(self->target_client);
+ }
+ if (self->source_client != NULL) {
+ Py_DECREF(self->source_client);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+ZoneLoader_load(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ try {
+ self->cppobj->load();
+ Py_RETURN_NONE;
+ } catch (const isc::InvalidOperation& ivo) {
+ PyErr_SetString(po_InvalidOperation, ivo.what());
+ return (NULL);
+ } catch (const isc::datasrc::MasterFileError& mfe) {
+ PyErr_SetString(getDataSourceException("MasterFileError"), mfe.what());
+ return (NULL);
+ } catch (const isc::datasrc::DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+}
+
+PyObject*
+ZoneLoader_loadIncremental(PyObject* po_self, PyObject* args) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+
+ int limit;
+ if (!PyArg_ParseTuple(args, "i", &limit)) {
+ return (NULL);
+ }
+ if (limit < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "load_incremental argument must be positive");
+ return (NULL);
+ }
+ try {
+ if (self->cppobj->loadIncremental(limit)) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const isc::InvalidOperation& ivo) {
+ PyErr_SetString(po_InvalidOperation, ivo.what());
+ return (NULL);
+ } catch (const isc::datasrc::MasterFileError& mfe) {
+ PyErr_SetString(getDataSourceException("MasterFileError"), mfe.what());
+ return (NULL);
+ } catch (const isc::datasrc::DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+}
+
+PyObject*
+ZoneLoader_getRRCount(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("I", self->cppobj->getRRCount()));
+}
+
+PyObject*
+ZoneLoader_getProgress(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("d", self->cppobj->getProgress()));
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef ZoneLoader_methods[] = {
+ { "load", ZoneLoader_load, METH_NOARGS, ZoneLoader_load_doc },
+ { "load_incremental", ZoneLoader_loadIncremental, METH_VARARGS,
+ ZoneLoader_loadIncremental_doc },
+ { "get_rr_count", ZoneLoader_getRRCount, METH_NOARGS,
+ ZoneLoader_getRRCount_doc },
+ { "get_progress", ZoneLoader_getProgress, METH_NOARGS,
+ ZoneLoader_getProgress_doc },
+ { NULL, NULL, 0, NULL }
+};
+
+} // end of unnamed namespace
+
+namespace isc {
+namespace datasrc {
+namespace python {
+
+PyTypeObject zone_loader_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.ZoneLoader",
+ sizeof(s_ZoneLoader), // tp_basicsize
+ 0, // tp_itemsize
+ ZoneLoader_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ ZoneLoader_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ ZoneLoader_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ ZoneLoader_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+
diff --git a/src/lib/python/isc/datasrc/zone_loader_python.h b/src/lib/python/isc/datasrc/zone_loader_python.h
new file mode 100644
index 0000000..f148efa
--- /dev/null
+++ b/src/lib/python/isc/datasrc/zone_loader_python.h
@@ -0,0 +1,34 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_DATASRC_ZONE_LOADER_H
+#define PYTHON_DATASRC_ZONE_LOADER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace datasrc {
+
+namespace python {
+
+extern PyTypeObject zone_loader_type;
+
+} // namespace python
+} // namespace datasrc
+} // namespace isc
+#endif // PYTHON_DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 7e34e70..406151c 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -122,7 +122,7 @@ as the class in the Zone Section, ANY, or NONE.
A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
-An error occured while committing the DDNS update changes to the
+An error occurred while committing the DDNS update changes to the
datasource. The specific error is printed. A SERVFAIL response is sent
back to the client.
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index 69e70b7..108efe3 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -40,7 +40,7 @@ using boost::bind;
// (tags/RELEASE_28 115909)) on OSX, where unwinding the stack
// segfaults the moment this exception was thrown and caught.
//
-// Placing it in a named namespace instead of the originalRecommend
+// Placing it in a named namespace instead of the original
// unnamed namespace appears to solve this, so as a temporary
// workaround, we create a local randomly named namespace here
// to solve this issue.
@@ -166,17 +166,23 @@ reset(PyObject*, PyObject*) {
}
PyObject*
-init(PyObject*, PyObject* args) {
+init(PyObject*, PyObject* args, PyObject* arg_keywords) {
const char* root;
const char* file(NULL);
const char* severity("INFO");
+ bool buffer = false;
int dbglevel(0);
- if (!PyArg_ParseTuple(args, "s|siz", &root, &severity, &dbglevel, &file)) {
+ const char* const keywords[] = { "name", "severity", "debuglevel", "file",
+ "buffer", NULL };
+ if (!PyArg_ParseTupleAndKeywords(args, arg_keywords, "s|sizb",
+ const_cast<char**>(keywords), &root,
+ &severity, &dbglevel, &file, &buffer)) {
return (NULL);
}
try {
- LoggerManager::init(root, getSeverity(severity), dbglevel, file);
+ LoggerManager::init(root, getSeverity(severity), dbglevel, file,
+ buffer);
}
catch (const std::exception& e) {
PyErr_SetString(PyExc_RuntimeError, e.what());
@@ -266,12 +272,19 @@ PyMethodDef methods[] = {
"need to call it. It returns None if the message does not exist."},
{"reset", reset, METH_NOARGS,
"Reset all logging. For testing purposes only, do not use."},
- {"init", init, METH_VARARGS,
+ {"init", reinterpret_cast<PyCFunction>(init), METH_VARARGS | METH_KEYWORDS,
"Run-time initialization. You need to call this before you do any "
"logging, to configure the root logger name. You may also provide "
- "logging severity (one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
- "'FATAL'), a debug level (integer in the range 0-99) and a file name "
- "of a dictionary with message text translations."},
+ "Arguments:\n"
+ "name: root logger name\n"
+ "severity (optional): one of 'DEBUG', 'INFO', 'WARN', 'ERROR' or "
+ "'FATAL'\n"
+ "debuglevel (optional): a debug level (integer in the range 0-99) "
+ "file (optional): a file name of a dictionary with message text "
+ "translations\n"
+ "buffer (optional), boolean, when True, causes all log messages "
+ "to be stored internally until log_config_update is called, at "
+ "which point they shall be logged."},
{"resetUnitTestRootLogger", resetUnitTestRootLogger, METH_VARARGS,
"Resets the configuration of the root logger to that set by the "
"B10_XXX environment variables. It is aimed at unit tests, where "
@@ -655,7 +668,7 @@ PyTypeObject logger_type = {
NULL, // tp_as_number
NULL, // tp_as_sequence
NULL, // tp_as_mapping
- NULL, // tp_hash
+ NULL, // tp_hash
NULL, // tp_call
NULL, // tp_str
NULL, // tp_getattro
diff --git a/src/lib/python/isc/log/tests/check_output.sh b/src/lib/python/isc/log/tests/check_output.sh
index 32146af..9499a85 100755
--- a/src/lib/python/isc/log/tests/check_output.sh
+++ b/src/lib/python/isc/log/tests/check_output.sh
@@ -1,3 +1,3 @@
#!/bin/sh
-"$1" 2>&1 | cut -d\ -f3- | diff - "$2" 1>&2
+"$1" 2>&1 | sed -e 's/\[\([a-z0-9\.]\{1,\}\)\/\([0-9]\{1,\}\)\]/[\1]/' | cut -d\ -f3- | diff - "$2" 1>&2
diff --git a/src/lib/python/isc/log/tests/log_test.py b/src/lib/python/isc/log/tests/log_test.py
index 1337654..2059caa 100644
--- a/src/lib/python/isc/log/tests/log_test.py
+++ b/src/lib/python/isc/log/tests/log_test.py
@@ -56,6 +56,28 @@ class Manager(unittest.TestCase):
# ignore errors like missing file?
isc.log.init("root", "INFO", 0, "/no/such/file");
+ def test_init_keywords(self):
+ isc.log.init(name="root", severity="DEBUG", debuglevel=50, file=None,
+ buffer=True)
+ # unknown keyword
+ self.assertRaises(TypeError, isc.log.init, name="root", foo="bar")
+ # Replace the values for each keyword by a wrong type, one by one
+ self.assertRaises(TypeError, isc.log.init, name=1,
+ severity="DEBUG", debuglevel=50, file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity=2, debuglevel=50, file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel="50", file=None,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel=50, file=1,
+ buffer=True)
+ self.assertRaises(TypeError, isc.log.init, name="root",
+ severity="DEBUG", debuglevel=50, file=None,
+ buffer=None)
+
def test_log_config_update(self):
log_spec = json.dumps(isc.config.module_spec_from_file(path_search('logging.spec', bind10_config.PLUGIN_PATHS)).get_full_spec())
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 6d23df3..97ff6e6 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -14,8 +14,10 @@ EXTRA_DIST += config_messages.py
EXTRA_DIST += notify_out_messages.py
EXTRA_DIST += libddns_messages.py
EXTRA_DIST += libxfrin_messages.py
+EXTRA_DIST += loadzone_messages.py
EXTRA_DIST += server_common_messages.py
EXTRA_DIST += dbutil_messages.py
+EXTRA_DIST += msgq_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -31,8 +33,10 @@ CLEANFILES += config_messages.pyc
CLEANFILES += notify_out_messages.pyc
CLEANFILES += libddns_messages.pyc
CLEANFILES += libxfrin_messages.pyc
+CLEANFILES += loadzone_messages.pyc
CLEANFILES += server_common_messages.pyc
CLEANFILES += dbutil_messages.pyc
+CLEANFILES += msgq_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/loadzone_messages.py b/src/lib/python/isc/log_messages/loadzone_messages.py
new file mode 100644
index 0000000..2374900
--- /dev/null
+++ b/src/lib/python/isc/log_messages/loadzone_messages.py
@@ -0,0 +1 @@
+from work.loadzone_messages import *
diff --git a/src/lib/python/isc/log_messages/msgq_messages.py b/src/lib/python/isc/log_messages/msgq_messages.py
new file mode 100644
index 0000000..81efa41
--- /dev/null
+++ b/src/lib/python/isc/log_messages/msgq_messages.py
@@ -0,0 +1 @@
+from work.msgq_messages import *
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
index 8da0b1e..99b989d 100644
--- a/src/lib/python/isc/statistics/counters.py
+++ b/src/lib/python/isc/statistics/counters.py
@@ -126,6 +126,10 @@ def _stop_timer(start_time, element, spec, identifier):
the element, which is in seconds between start_time and the
current time and is float-type."""
delta = datetime.now() - start_time
+ # FIXME: The following statement can be replaced by:
+ # sec = delta.total_seconds()
+ # but total_seconds() is not available in Python 3.1. Please update
+ # this code when we depend on Python 3.2.
sec = round(delta.days * 86400 + delta.seconds + \
delta.microseconds * 1E-6, 6)
_set_counter(element, spec, identifier, sec)
diff --git a/src/lib/resolve/resolve_messages.mes b/src/lib/resolve/resolve_messages.mes
index 1df544a..6447082 100644
--- a/src/lib/resolve/resolve_messages.mes
+++ b/src/lib/resolve/resolve_messages.mes
@@ -86,7 +86,7 @@ SERVFAIL will be returned.
% RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response
A debug message, the response to the specified query from an upstream
-nameserver was a CNAME that had mutiple RRs in the RRset. This is
+nameserver was a CNAME that had multiple RRs in the RRset. This is
an invalid response according to the standards so a SERVFAIL will be
returned to the system making the original query.
@@ -138,7 +138,7 @@ A debug message, the response to the specified query indicated an error
that is not covered by a specific code path. A SERVFAIL will be returned.
% RESLIB_RECQ_CACHE_FIND found <%1> in the cache (resolve() instance %2)
-This is a debug message and indicates that a RecursiveQuery object found the
+This is a debug message and indicates that a RecursiveQuery object found
the specified <name, class, type> tuple in the cache. The instance number
at the end of the message indicates which of the two resolve() methods has
been called.
@@ -178,7 +178,7 @@ A debug message giving the round-trip time of the last query and response.
This is a debug message and indicates that a RunningQuery object found
the specified <name, class, type> tuple in the cache.
-% RESLIB_RUNQ_CACHE_LOOKUP looking up up <%1> in the cache
+% RESLIB_RUNQ_CACHE_LOOKUP looking up <%1> in the cache
This is a debug message and indicates that a RunningQuery object has made
a call to its doLookup() method to look up the specified <name, class, type>
tuple, the first action of which will be to examine the cache.
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 0e4efa5..22ce0f3 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -17,11 +17,11 @@ $NAMESPACE isc::server_common
# \brief Messages for the server_common library
% SOCKETREQUESTOR_CREATED Socket requestor created for application %1
-Debug message. A socket requesor (client of the socket creator) is created
+Debug message. A socket requestor (client of the socket creator) is created
for the corresponding application. Normally this should happen at most
one time throughout the lifetime of the application.
-% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+% SOCKETREQUESTOR_DESTROYED Socket requestor destroyed
Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
has been destroyed. This event is generally unexpected other than in
test cases.
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 262d177..6de01ec 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -208,10 +208,9 @@ pullSigs(std::vector<isc::dns::ConstRRsetPtr>& rrsets,
{
for (ITERATOR it = begin; it != end; ++it) {
rrsets.push_back(*it);
- text += (*it)->toText();
+ text += (*it)->toText(); // this will include RRSIG, if attached.
if ((*it)->getRRsig()) {
rrsets.push_back((*it)->getRRsig());
- text += (*it)->getRRsig()->toText();
}
}
}
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index d686da6..7b0b1bb 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -27,6 +27,8 @@
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::dns;
using namespace isc::util;
using namespace isc::asiolink;
@@ -48,27 +50,20 @@ SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
response_obuffer(new OutputBuffer(0))
{}
-SrvTestBase::~SrvTestBase() {
- delete io_message;
- delete endpoint;
-}
-
void
SrvTestBase::createDataFromFile(const char* const datafile,
const int protocol)
{
- delete io_message;
data.clear();
- delete endpoint;
-
- endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS),
- DEFAULT_REMOTE_PORT);
+ endpoint.reset(IOEndpoint::create(protocol,
+ IOAddress(DEFAULT_REMOTE_ADDRESS),
+ DEFAULT_REMOTE_PORT));
UnitTestUtil::readWireData(datafile, data);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
- io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+ io_message.reset(new IOMessage(&data[0], data.size(), *io_sock,
+ *endpoint));
}
void
@@ -83,16 +78,14 @@ SrvTestBase::createRequestPacket(Message& message,
message.toWire(request_renderer, *context);
}
- delete io_message;
-
- endpoint = IOEndpoint::create(protocol, IOAddress(remote_address),
- remote_port);
+ endpoint.reset(IOEndpoint::create(protocol, IOAddress(remote_address),
+ remote_port));
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
- io_message = new IOMessage(request_renderer.getData(),
- request_renderer.getLength(),
- *io_sock, *endpoint);
+ io_message.reset(new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ *io_sock, *endpoint));
}
// Unsupported requests. Should result in NOTIMP.
@@ -151,7 +144,7 @@ SrvTestBase::shortMessage() {
// or malformed or could otherwise cause a protocol error.
void
SrvTestBase::response() {
- // A valid (although unusual) response
+ // A valid (although unusual) response
createDataFromFile("simpleresponse_fromWire.wire");
processMessage();
EXPECT_FALSE(dnsserv.hasAnswer());
@@ -242,6 +235,6 @@ SrvTestBase::axfrOverUDP() {
} // end of namespace isc
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index e867595..a37d79b 100644
--- a/src/lib/testutils/srv_test.h
+++ b/src/lib/testutils/srv_test.h
@@ -25,6 +25,7 @@
#include <dns/rrtype.h>
#include "mockups.h"
+#include <boost/scoped_ptr.hpp>
namespace asiolink {
class IOSocket;
@@ -52,7 +53,6 @@ extern const unsigned int CD_FLAG;
class SrvTestBase : public ::testing::Test {
protected:
SrvTestBase();
- virtual ~SrvTestBase();
/// Let the server process a DNS message.
///
@@ -104,8 +104,8 @@ protected:
const isc::dns::RRClass qclass;
const isc::dns::RRType qtype;
asiolink::IOSocket* io_sock;
- asiolink::IOMessage* io_message;
- const asiolink::IOEndpoint* endpoint;
+ boost::scoped_ptr<asiolink::IOMessage> io_message;
+ boost::scoped_ptr<const asiolink::IOEndpoint> endpoint;
isc::dns::MessageRenderer request_renderer;
isc::util::OutputBufferPtr response_obuffer;
std::vector<uint8_t> data;
@@ -114,6 +114,6 @@ protected:
} // end of namespace isc
#endif // ISC_TESTUTILS_SRVTEST_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/util/encode/base_n.cc b/src/lib/util/encode/base_n.cc
index 0026a0b..c38f901 100644
--- a/src/lib/util/encode/base_n.cc
+++ b/src/lib/util/encode/base_n.cc
@@ -291,7 +291,8 @@ BaseNTransformer<BitsPerChunk, BaseZeroCode, Encoder, Decoder>::decode(
isc_throw(BadValue, "Too many " << algorithm
<< " padding characters: " << input);
}
- } else if (ch < 0 || !isspace(ch)) {
+ } else if (!(ch > 0 && isspace(ch))) {
+ // see the note for DecodeNormalizer::skipSpaces() above for ch > 0
break;
}
++srit;
diff --git a/src/lib/util/python/gen_wiredata.py.in b/src/lib/util/python/gen_wiredata.py.in
index f997701..55724c9 100755
--- a/src/lib/util/python/gen_wiredata.py.in
+++ b/src/lib/util/python/gen_wiredata.py.in
@@ -770,7 +770,7 @@ class TXT(RR):
nstring = 1
stringlen = None
- string = 'Test String'
+ string = 'Test-String'
def dump(self, f):
stringlen_list = []
diff --git a/tests/lettuce/configurations/xfrin/.gitignore b/tests/lettuce/configurations/xfrin/.gitignore
index 4de8c4b..5d56912 100644
--- a/tests/lettuce/configurations/xfrin/.gitignore
+++ b/tests/lettuce/configurations/xfrin/.gitignore
@@ -1 +1,2 @@
/retransfer_master.conf
+/retransfer_slave.conf
diff --git a/tests/lettuce/features/msgq.feature b/tests/lettuce/features/msgq.feature
new file mode 100644
index 0000000..19973f4
--- /dev/null
+++ b/tests/lettuce/features/msgq.feature
@@ -0,0 +1,18 @@
+Feature: Message queue tests
+ Tests for the message queue daemon.
+
+ Scenario: logging
+ # We check the message queue logs.
+ Given I have bind10 running with configuration default.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message MSGQ_START
+ And wait for bind10 stderr message MSGQ_LISTENER_STARTED
+ And wait for bind10 stderr message MSGQ_CFGMGR_SUBSCRIBED
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # Check it handles configuration. The configuration is invalid,
+ # but it should get there anyway and we abuse it.
+ # TODO: Once it has any kind of real command or configuration
+ # value, use that instead.
+ Then set bind10 configuration Msgq to {"nonsense": 1}
+ And wait for bind10 stderr message MSGQ_CONFIG_DATA
diff --git a/tests/lettuce/features/xfrin_bind10.feature b/tests/lettuce/features/xfrin_bind10.feature
index 3c7fa1c..34674ca 100644
--- a/tests/lettuce/features/xfrin_bind10.feature
+++ b/tests/lettuce/features/xfrin_bind10.feature
@@ -79,7 +79,7 @@ Feature: Xfrin
When I send bind10 the following commands:
"""
config add tsig_keys/keys "example.key.:c2VjcmV0"
- config set Xfrin/zones[0]/tsig_key "example.key.:c2VjcmV0"
+ config set Xfrin/zones[0]/tsig_key "example.key."
config commit
"""
diff --git a/tests/system/bindctl/setup.sh b/tests/system/bindctl/setup.sh
index 55afcc1..7a9a323 100755
--- a/tests/system/bindctl/setup.sh
+++ b/tests/system/bindctl/setup.sh
@@ -22,5 +22,5 @@ SUBTEST_TOP=${TEST_TOP}/bindctl
cp ${SUBTEST_TOP}/nsx1/b10-config.db.template ${SUBTEST_TOP}/nsx1/b10-config.db
rm -f ${SUBTEST_TOP}/*/zone.sqlite3
-${B10_LOADZONE} -o . -d ${SUBTEST_TOP}/nsx1/zone.sqlite3 \
- ${SUBTEST_TOP}//nsx1/root.db
+${B10_LOADZONE} -i 1 -c "{\"database_file\": \"${SUBTEST_TOP}/nsx1/zone.sqlite3\"}" \
+ . ${SUBTEST_TOP}//nsx1/root.db
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
index 9611c90..75c91de 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -85,6 +85,7 @@ config commit
quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
# perform a simple check on the output (digcomp would be too much for this)
grep 192.0.2.1 dig.out.$n > /dev/null || status=1
@@ -118,6 +119,7 @@ config commit
quit
" | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR > bindctl.out.$n || status=1
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
@@ -148,6 +150,7 @@ quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
done
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
@@ -183,6 +186,7 @@ quit
' | $RUN_BINDCTL \
--csv-file-dir=$BINDCTL_CSV_DIR 2>&1 > /dev/null || status=1
done
+sleep 2
$DIG +norec @10.53.0.1 -p 53210 ns.example.com. A >dig.out.$n || status=1
grep 192.0.2.2 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
diff --git a/tests/system/conf.sh.in b/tests/system/conf.sh.in
index 92f72fa..4948d63 100755
--- a/tests/system/conf.sh.in
+++ b/tests/system/conf.sh.in
@@ -51,7 +51,10 @@ export RNDC=$BIND9_TOP/bin/rndc/rndc
export TESTSOCK=$BIND9_TOP/bin/tests/system/testsock.pl
export DIGCOMP=$BIND9_TOP/bin/tests/system/digcomp.pl
-export SUBDIRS="bindctl glue ixfr/in-2"
+# bindctl test doesn't work right now and is disabled (see #2568)
+#export SUBDIRS="bindctl glue ixfr/in-2"
+export SUBDIRS="glue ixfr/in-2"
+
# Add appropriate subdirectories to the above statement as the tests become
# available.
#SUBDIRS="dnssec masterfile ixfr/in-1 ixfr/in-2 ixfr/in-4"
diff --git a/tests/system/glue/setup.sh.in b/tests/system/glue/setup.sh.in
index dc5b28a..2f9f886 100755
--- a/tests/system/glue/setup.sh.in
+++ b/tests/system/glue/setup.sh.in
@@ -18,8 +18,8 @@ SYSTEMTESTTOP=..
. $SYSTEMTESTTOP/conf.sh
rm -f */zone.sqlite3
-${B10_LOADZONE} -o . -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/root.db
-${B10_LOADZONE} -o root-servers.nil -d @builddir@/nsx1/zone.sqlite3 \
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' . @builddir@/nsx1/root.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' root-servers.nil \
@builddir@/nsx1/root-servers.nil.db
-${B10_LOADZONE} -o com -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/com.db
-${B10_LOADZONE} -o net -d @builddir@/nsx1/zone.sqlite3 @builddir@/nsx1/net.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' com @builddir@/nsx1/com.db
+${B10_LOADZONE} -i 1 -c '{"database_file": "@builddir@/nsx1/zone.sqlite3"}' net @builddir@/nsx1/net.db
diff --git a/tests/system/ixfr/in-1/setup.sh.in b/tests/system/ixfr/in-1/setup.sh.in
index d4c3978..4332930 100644
--- a/tests/system/ixfr/in-1/setup.sh.in
+++ b/tests/system/ixfr/in-1/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n4 ns1/db.example
# Set up the IXFR client - load the same version of the zone.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n4
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n4
diff --git a/tests/system/ixfr/in-2/setup.sh.in b/tests/system/ixfr/in-2/setup.sh.in
index a5f64e5..a210636 100644
--- a/tests/system/ixfr/in-2/setup.sh.in
+++ b/tests/system/ixfr/in-2/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n6 ns1/db.example
# Set up the IXFR client - load an earlier version of the zone
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n6
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n6
diff --git a/tests/system/ixfr/in-3/setup.sh.in b/tests/system/ixfr/in-3/setup.sh.in
index 867e06e..2a08c58 100644
--- a/tests/system/ixfr/in-3/setup.sh.in
+++ b/tests/system/ixfr/in-3/setup.sh.in
@@ -26,4 +26,4 @@ cp -f $IXFR_TOP/db.example.n0 ns1/db.example
# Set up the IXFR client - load a previous version of the zone.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2
diff --git a/tests/system/ixfr/in-4/setup.sh.in b/tests/system/ixfr/in-4/setup.sh.in
index 7419e27..1c2e9c8 100644
--- a/tests/system/ixfr/in-4/setup.sh.in
+++ b/tests/system/ixfr/in-4/setup.sh.in
@@ -27,4 +27,4 @@ cp -f $IXFR_TOP/db.example.n2.refresh ns1/db.example
# Set up the IXFR client - load a previous version of the zone with a short
# refresh time.
cp -f $IXFR_TOP/b10-config.db nsx2/b10-config.db
-${B10_LOADZONE} -o . -d $IXFR_TOP/zone.sqlite3 $IXFR_TOP/db.example.n2.refresh
+${B10_LOADZONE} -c "{\"database_file\": \"$IXFR_TOP/zone.sqlite3\"}" example. $IXFR_TOP/db.example.n2.refresh
diff --git a/tests/tools/perfdhcp/Makefile.am b/tests/tools/perfdhcp/Makefile.am
index 08a21a4..c4b82b5 100644
--- a/tests/tools/perfdhcp/Makefile.am
+++ b/tests/tools/perfdhcp/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = . tests templates
+SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/log -I$(top_builddir)/src/lib/log
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 3ccef94..8b7e974 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -13,7 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/libdhcp++.h>
-#include <dhcp/dhcp6.h>
+#include <dhcp/dhcp4.h>
#include "perf_pkt4.h"
diff --git a/tests/tools/perfdhcp/pkt_transform.cc b/tests/tools/perfdhcp/pkt_transform.cc
index b1c64e2..15f15f1 100644
--- a/tests/tools/perfdhcp/pkt_transform.cc
+++ b/tests/tools/perfdhcp/pkt_transform.cc
@@ -196,12 +196,10 @@ PktTransform::unpackOptions(const OptionBuffer& in_buffer,
// Get option length which is supposed to be after option type.
offset += offset_step;
- uint16_t opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
- if (option->getUniverse() == Option::V6) {
- opt_len = in_buffer[offset] * 256 + in_buffer[offset + 1];
- } else {
- opt_len = in_buffer[offset];
- }
+ const uint16_t opt_len =
+ (option->getUniverse() == Option::V6) ?
+ in_buffer[offset] * 256 + in_buffer[offset + 1] :
+ in_buffer[offset];
// Check if packet is not truncated.
if (offset + option->getHeaderLen() + opt_len > in_buffer.size()) {
diff --git a/tests/tools/perfdhcp/templates/.gitignore b/tests/tools/perfdhcp/templates/.gitignore
deleted file mode 100644
index 6f865da..0000000
--- a/tests/tools/perfdhcp/templates/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-/test1.hex
-/test2.hex
-/test3.hex
-/test4.hex
-/test5.hex
diff --git a/tests/tools/perfdhcp/templates/Makefile.am b/tests/tools/perfdhcp/templates/Makefile.am
deleted file mode 100644
index c22787f..0000000
--- a/tests/tools/perfdhcp/templates/Makefile.am
+++ /dev/null
@@ -1,10 +0,0 @@
-SUBDIRS = .
-
-# The test[1-5].hex are created by the TestControl.PacketTemplates
-# unit tests and have to be removed.
-CLEANFILES = test1.hex test2.hex test3.hex test4.hex test5.hex
-
-perfdhcpdir = $(pkgdatadir)
-
-EXTRA_DIST = discover-example.hex request4-example.hex
-EXTRA_DIST += solicit-example.hex request6-example.hex
diff --git a/tests/tools/perfdhcp/templates/discover-example.hex b/tests/tools/perfdhcp/templates/discover-example.hex
deleted file mode 100644
index 9a6e5ea..0000000
--- a/tests/tools/perfdhcp/templates/discover-example.hex
+++ /dev/null
@@ -1 +0,0 @@
-01010601008b45d200000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060cff
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/templates/request4-example.hex b/tests/tools/perfdhcp/templates/request4-example.hex
deleted file mode 100644
index 32447d6..0000000
--- a/tests/tools/perfdhcp/templates/request4-example.hex
+++ /dev/null
@@ -1 +0,0 @@
-01010601007b23f800000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633204ac1001813501033604ac1001013707011c02030f060cff
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/templates/request6-example.hex b/tests/tools/perfdhcp/templates/request6-example.hex
deleted file mode 100644
index 1e3e76f..0000000
--- a/tests/tools/perfdhcp/templates/request6-example.hex
+++ /dev/null
@@ -1 +0,0 @@
-03da30c60001000e0001000117cf8e76000c010203060002000e0001000117cf8a5c080027a87b3400030028000000010000000a0000000e0005001820010db800010000000000000001b568000000be000000c8000800020000
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/templates/solicit-example.hex b/tests/tools/perfdhcp/templates/solicit-example.hex
deleted file mode 100644
index 41c5ad3..0000000
--- a/tests/tools/perfdhcp/templates/solicit-example.hex
+++ /dev/null
@@ -1 +0,0 @@
-015f4e650001000e0001000117cf8e76000c010203040003000c0000000100000e01000015180006000400170018000800020000
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 9376972..f7f4978 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -1284,6 +1284,10 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
if (!pkt4) {
isc_throw(Unexpected, "failed to create DISCOVER packet");
}
+
+ // Delete the default Message Type option set by Pkt4
+ pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
+
// Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
OptionBuffer buf_msg_type;
buf_msg_type.push_back(DHCPDISCOVER);
@@ -1371,11 +1375,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
const dhcp::Pkt4Ptr& offer_pkt4) {
const uint32_t transid = generateTransid();
Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
- OptionBuffer buf_msg_type;
- buf_msg_type.push_back(DHCPREQUEST);
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- buf_msg_type);
- pkt4->addOption(opt_msg_type);
+
// Use first flags indicates that we want to use the server
// id captured in first packet.
if (CommandOptions::instance().isUseFirst() &&
@@ -1414,9 +1414,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
setDefaults4(socket, pkt4);
// Set hardware address
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
- pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
+ pkt4->setHWAddr(offer_pkt4->getHWAddr());
// Set elapsed time.
uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
@@ -1461,8 +1459,10 @@ TestControl::sendRequest4(const TestControlSocket& socket,
transid));
// Set hardware address from OFFER packet received.
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
+ HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
+ std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
+ uint8_t hw_len = hwaddr->hwaddr_.size();
+ memcpy(&mac_address[0], &hwaddr->hwaddr_[0], hw_len > 16 ? 16 : hw_len);
pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
// Set elapsed time.
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
index d6d1ec8..5697f95 100644
--- a/tests/tools/perfdhcp/tests/.gitignore
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -1 +1,6 @@
/run_unittests
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex
diff --git a/tests/tools/perfdhcp/tests/Makefile.am b/tests/tools/perfdhcp/tests/Makefile.am
index 54602af..aa4c0cf 100644
--- a/tests/tools/perfdhcp/tests/Makefile.am
+++ b/tests/tools/perfdhcp/tests/Makefile.am
@@ -1,6 +1,7 @@
-SUBDIRS = .
+SUBDIRS = . testdata
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_srcdir)/testdata\"
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(B10_CXXFLAGS)
@@ -9,6 +10,9 @@ AM_LDFLAGS = -static
endif
CLEANFILES = *.gcno *.gcda
+# The test[1-5].hex are created by the TestControl.PacketTemplates
+# unit tests and have to be removed.
+CLEANFILES += test1.hex test2.hex test3.hex test4.hex test5.hex
TESTS_ENVIRONMENT = \
$(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index 66f85fe..ae67e6e 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -185,6 +185,18 @@ public:
return ("");
}
+ /// \brief Get full path to a file in testdata directory.
+ ///
+ /// \param filename filename being appended to absolute
+ /// path to testdata directory
+ ///
+ /// \return full path to a file in testdata directory.
+ std::string getFullPath(const std::string& filename) const {
+ std::ostringstream stream;
+ stream << TEST_DATA_DIR << "/" << filename;
+ return (stream.str());
+ }
+
/// \brief Match requested options in the buffer with given list.
///
/// This method iterates through options provided in the buffer
@@ -618,13 +630,10 @@ private:
boost::shared_ptr<Pkt4>
createOfferPkt4(uint32_t transid) const {
boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- OptionBuffer(DHCPOFFER));
OptionPtr opt_serverid = Option::factory(Option::V4,
DHO_DHCP_SERVER_IDENTIFIER,
OptionBuffer(4, 1));
offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
- offer->addOption(opt_msg_type);
offer->addOption(opt_serverid);
offer->updateTimestamp();
return (offer);
@@ -925,8 +934,8 @@ TEST_F(TestControlTest, Packet4Exchange) {
// Use templates for this test.
processCmdLine("perfdhcp -l " + loopback_iface
+ " -r 100 -R 20 -n 20 -D 10% -L 10547"
- + " -T ../templates/discover-example.hex"
- + " -T ../templates/request4-example.hex"
+ + " -T " + getFullPath("discover-example.hex")
+ + " -T " + getFullPath("request4-example.hex")
+ " 127.0.0.1");
// The number iterations is restricted by the percentage of
// dropped packets (-D 10%). We also have to bump up the number
@@ -967,8 +976,8 @@ TEST_F(TestControlTest, Packet6Exchange) {
use_templates = true;
processCmdLine("perfdhcp -l " + loopback_iface
+ " -6 -r 100 -n 10 -R 20 -D 3 -L 10547"
- + " -T ../templates/solicit-example.hex"
- + " -T ../templates/request6-example.hex ::1");
+ + " -T " + getFullPath("solicit-example.hex")
+ + " -T " + getFullPath("request6-example.hex ::1"));
// For the first 3 packets we are simulating responses from server.
// For other packets we don't so packet as 4,5,6 will be dropped and
// then test should be interrupted and actual number of iterations will
@@ -981,9 +990,9 @@ TEST_F(TestControlTest, Packet6Exchange) {
TEST_F(TestControlTest, PacketTemplates) {
std::vector<uint8_t> template1(256);
- std::string file1("../templates/test1.hex");
+ std::string file1("test1.hex");
std::vector<uint8_t> template2(233);
- std::string file2("../templates/test2.hex");
+ std::string file2("test2.hex");
for (int i = 0; i < template1.size(); ++i) {
template1[i] = static_cast<uint8_t>(random() % 256);
}
@@ -1011,7 +1020,7 @@ TEST_F(TestControlTest, PacketTemplates) {
EXPECT_TRUE(std::equal(template2.begin(), template2.end(), buf2.begin()));
// Try to read template file with odd number of digits.
- std::string file3("../templates/test3.hex");
+ std::string file3("test3.hex");
// Size of the file is 2 times larger than binary data size and it is always
// even number. Substracting 1 makes file size odd.
ASSERT_TRUE(createTemplateFile(file3, template1, template1.size() * 2 - 1));
@@ -1021,7 +1030,7 @@ TEST_F(TestControlTest, PacketTemplates) {
EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange);
// Try to read empty file.
- std::string file4("../templates/test4.hex");
+ std::string file4("test4.hex");
ASSERT_TRUE(createTemplateFile(file4, template2, 0));
ASSERT_NO_THROW(
processCmdLine("perfdhcp -l 127.0.0.1 -T " + file4 + " all")
@@ -1029,7 +1038,7 @@ TEST_F(TestControlTest, PacketTemplates) {
EXPECT_THROW(tc.initPacketTemplates(), isc::OutOfRange);
// Try reading file with non hexadecimal characters.
- std::string file5("../templates/test5.hex");
+ std::string file5("test5.hex");
ASSERT_TRUE(createTemplateFile(file5, template1, template1.size() * 2, true));
ASSERT_NO_THROW(
processCmdLine("perfdhcp -l 127.0.0.1 -T " + file5 + " all")
diff --git a/tests/tools/perfdhcp/tests/testdata/.gitignore b/tests/tools/perfdhcp/tests/testdata/.gitignore
new file mode 100644
index 0000000..6f865da
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/.gitignore
@@ -0,0 +1,5 @@
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex
diff --git a/tests/tools/perfdhcp/tests/testdata/Makefile.am b/tests/tools/perfdhcp/tests/testdata/Makefile.am
new file mode 100644
index 0000000..2de1643
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/Makefile.am
@@ -0,0 +1,4 @@
+SUBDIRS = .
+
+EXTRA_DIST = discover-example.hex request4-example.hex
+EXTRA_DIST += solicit-example.hex request6-example.hex
diff --git a/tests/tools/perfdhcp/tests/testdata/discover-example.hex b/tests/tools/perfdhcp/tests/testdata/discover-example.hex
new file mode 100644
index 0000000..9a6e5ea
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/discover-example.hex
@@ -0,0 +1 @@
+01010601008b45d200000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633501013707011c02030f060cff
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/tests/testdata/request4-example.hex b/tests/tools/perfdhcp/tests/testdata/request4-example.hex
new file mode 100644
index 0000000..32447d6
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/request4-example.hex
@@ -0,0 +1 @@
+01010601007b23f800000000000000000000000000000000ac100102000c0102030400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000638253633204ac1001813501033604ac1001013707011c02030f060cff
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/tests/testdata/request6-example.hex b/tests/tools/perfdhcp/tests/testdata/request6-example.hex
new file mode 100644
index 0000000..1e3e76f
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/request6-example.hex
@@ -0,0 +1 @@
+03da30c60001000e0001000117cf8e76000c010203060002000e0001000117cf8a5c080027a87b3400030028000000010000000a0000000e0005001820010db800010000000000000001b568000000be000000c8000800020000
\ No newline at end of file
diff --git a/tests/tools/perfdhcp/tests/testdata/solicit-example.hex b/tests/tools/perfdhcp/tests/testdata/solicit-example.hex
new file mode 100644
index 0000000..41c5ad3
--- /dev/null
+++ b/tests/tools/perfdhcp/tests/testdata/solicit-example.hex
@@ -0,0 +1 @@
+015f4e650001000e0001000117cf8e76000c010203040003000c0000000100000e01000015180006000400170018000800020000
\ No newline at end of file
More information about the bind10-changes
mailing list