BIND 10 trac2524, updated. c55cdfd6318072c9ae7005298fca07615065c97a [2524] Add logging to lease manager factory and MySQL lease manager
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Dec 14 22:20:03 UTC 2012
The branch, trac2524 has been updated
via c55cdfd6318072c9ae7005298fca07615065c97a (commit)
via be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
via e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
via 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
via a7e16e85fac60062df06be2f371868571d08e5ed (commit)
via 8652d1d4c6e2f9538b20443eb0cd0f37672915ef (commit)
via ac36820764982483b3b25a2d2ed6e05cea6cf322 (commit)
via 6260781b99d5b7c1f495e584abe9f74ea4e33e03 (commit)
via 2c565f3b83fd09baffbeb82a7e8a4c56990f45dd (commit)
via ea9d025cbcd9a318a2946c1a7f00283885ff381f (commit)
via 5d7e273a02e5f5a1f83db02260a2a22e729f3a10 (commit)
via 57ad11888879be61a4d877b5ea4a29d504263e90 (commit)
via b25d6802e2236e06c9eb83a9508cb9bfca048563 (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 3666cb4d97d21903f1637796edab95bb466c213e (commit)
via 42c5261913f5277e51be25a4e3de08223cf8949a (commit)
via fe8a9e7c2c315207cf643f27115a78e8b2b4eb7f (commit)
via 550a5e0da25f7b3fa8051d988d70c116490b0f74 (commit)
via 286787bed2ffcf6ea6ff2da240edc9f78ef0660d (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 a56f7ff8c099d2d97a177cba073ee413ba3e9bd9 (commit)
via 6fe86386be0e7598633fe35999112c1a6e3b0370 (commit)
via b193ad87c312d2e6ceb54f0f2746425ba4f59d63 (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 a40fac0707fac08e85b06974092bbdc15720f37f (commit)
via 196c4d239ece786aa29b86e6b48bef337ca330fc (commit)
via 45e97b85271f437e9f3897fc78e82cbac8f2368a (commit)
via 1f943baf9448dcec53f5bcde3e9d21fae2eeb121 (commit)
via 0462436914ec6bccff8e7825ba3f55d50acda03e (commit)
via e65b7b36f60f14b7abe083da411e6934cdfbae7a (commit)
via 33fb59eeb630ce1fb6055a6a8d26c46951fc9c78 (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 422a1fcd67e40f3e398c292b50952cc09bce2623 (commit)
via 044c4779138290c78c20018d7f083aad7604d823 (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 85c5f47252f0db064ece1a00c121cca9519d3b42 (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 cc62eeca9e859daf93603bc2421f3f2988870d1c (commit)
via 52a60eb5c950491c450ec933f66a56318b4da669 (commit)
via e5fb484fbabf454dc0fea14ed4564061555af275 (commit)
via 03911f1000649e0db11d1bbd956b2df5732a5a44 (commit)
via 331a48e08d6c155f68eda2218fc04ec7cc276cbb (commit)
via 22d72fe82da9313ecd73bae3efa61788f0b8c7dd (commit)
via 512ccc6d8b7bc49b085289226407fe741ebe7a54 (commit)
via 1378932f029f1e74cc606013778863161feca8e4 (commit)
via dfebd6128b4088ba489080cea7c53f141dce90ab (commit)
via f2a0e7ce76ed227a7067919a1f44cb33ccf1e826 (commit)
via e6d402802cf16fb0d3c183ea32658e4e7b6e278b (commit)
via 44f835de6b8c19c7b6ffe755d956d24da061559e (commit)
via 2f3467a4eaeea16afe3119ab6f6628ed1a7f941a (commit)
via 9ea3182d49d0ad15571ea962ae34b68bcf5a200f (commit)
via d9d6998c28cfcf825fb2845acc3b4eb705e991a4 (commit)
via 9430b1fef5b2cc99e97b4cfaf6ccd518bcd76198 (commit)
via 0d69a3ddefcc66ffcf05683763ef2c7f27b7c392 (commit)
via c59714604459a820819a288ff03c4dadebda098b (commit)
via 7e8e8bcaaba5f836b3868f6601e8b4e8314a431e (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 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 c831a1e66e88bceb6a317dd8de0796b315681f4f (commit)
via d506b61e5f2e50d228ac8541d6927594861a25e5 (commit)
via 38db6aff7d759e5f271cf0d369459b361c8e60a7 (commit)
via d6fb00a3198fe327cdbf4ebfe14f47eb23086325 (commit)
via f2a03c4538343e2a6941e5037925a63a7e71f68c (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 5ec0945ea8abd8ce3aa59aa851421269d9f807d4 (commit)
via 9b8747917cc300904783b403a64c2a11ebdb5ec0 (commit)
via 11ed5b5ca1fb48c7754c1d85b65fa00b00630d78 (commit)
via ad9807ad75f8b76413de4ab0478cff9611a2817a (commit)
via daca34509f65af73d06ce071340668258539f6c6 (commit)
via 650e0b501ab48af61704efe9cb503490716c8eec (commit)
via 45c04beca6f7d07f1a2c779baf3a7a8ba26763a2 (commit)
via 0b06fc6c1993467408bdb7f109affe029a834e3d (commit)
via bc44ee8bf3c35cacaad05751d75e8789aa5c9108 (commit)
via b561ee0919676f6e9c34329151c2b46d60f1824b (commit)
via 372f3a19f5bad9ed51e50d083063fccbe1ff6900 (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 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 7cf0d504794bbf42d15015af1d42ba1cf7fcd82f (commit)
via ad7b96467ceeeade2ca9bc16ba8f02ecadc1ed82 (commit)
via a5330334a54791088394fe6b726666b9ae6671b9 (commit)
via 9c4e97e7f8cfe7f8001c1491f9f7c2e632c736b2 (commit)
via 1dc0f0871d7045e8fb57bb0767fc31a2f7e3fe80 (commit)
via 1bbe13152cea9e759bb0423750f244be58eff578 (commit)
via 8a21830a250996c93b1b053c56a4dc9bb7bb4750 (commit)
via ac111116c588c5b611e1363d4fe11c7ee8f4e495 (commit)
via bbefd6bc4137c3345845d09e86be68b81770718f (commit)
via 84e441c75606aff87131e7d84010679a11f242ce (commit)
via 159af7d5de17a74269cd4af69ac3b569495c4a4e (commit)
via 54663a3c7bda67a6813d2478ff44c8a07d9c48b8 (commit)
via 00eee13bde95c741890ad3ca52967c11076c5087 (commit)
via 22a33dfddc0b29a506b57c3f76bace8d15ec6df6 (commit)
via 7793f98f619c3422ca3fa26795a5861b0fbab4fa (commit)
via c806c0c3184caf046e124f36028122a965078408 (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 cfdfb58ce442816cce069453d4051cc008691b74 (commit)
via 08a5a3d31220c660530bd69cccc8c27e1a789312 (commit)
via 7f6ecb5e40c6acede82fb08a7d4c477030a48b99 (commit)
via f6c776bc4a3cea0eae628d1580ab10843be06dfe (commit)
via bc4926dac23a819a909992abf58df64d62647c68 (commit)
via fa3e24bb027671b5c53a5fa1b004920cf8252f0f (commit)
via a11abf30b29eda75f8680c8287b57db1abe612e8 (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 5450b258dbeaaf876fd35c52a1191e4f083a620e (commit)
via b1c8e1ce93d760fa32e7b977910faa9ed5fc4c30 (commit)
via 58f0aac3a8f36f335c0546f44122ff43f42f5761 (commit)
via 84fbdaeddb176af0a5cd73caf76698208b4e94a7 (commit)
via 09f21f49fa1e214af117262c305efb3968becd8f (commit)
via 36d2b848c6badf00bae61bd958b891b7e2acc8cd (commit)
via 4f52e898a7b69228cf4a8ab70d78844ad9dc45e1 (commit)
via c2c64eae1f3e6140bbe30e963797b9a4efa5a9f8 (commit)
via 230de14b1b3e31b2f436cfd370e149a98926522e (commit)
via 54a707d39ae1943731ed2043f8d44424c434f0e3 (commit)
via 51bd304db036e8d2e6008a98b2dae01eea126e41 (commit)
via da1b2d7dc0a367ed168b366df1ab5c5af82260d4 (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)
from 93eddf78d2db40f314323dc708252c87ed846a48 (commit)
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 c55cdfd6318072c9ae7005298fca07615065c97a
Author: Stephen Morris <stephen at isc.org>
Date: Fri Dec 14 22:19:31 2012 +0000
[2524] Add logging to lease manager factory and MySQL lease manager
commit be90b92cbe913d2eda9b95f18c099669d0a72a57
Author: Stephen Morris <stephen at isc.org>
Date: Fri Dec 14 22:17:19 2012 +0000
[2524] Add ClientId::toText() functionality
commit e632c67d9f527acf7cea58af306057e5ba5601a4
Author: Stephen Morris <stephen at isc.org>
Date: Fri Dec 14 16:15:23 2012 +0000
[2524] Add declaration and definition of logger, and some messages
commit 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572
Author: Stephen Morris <stephen at isc.org>
Date: Tue Dec 11 18:16:38 2012 +0000
[2524] Add method to redact password from access string
The access string is logged: as it includes the password, this
is removed from the message logged.
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 59 +-
configure.ac | 4 +-
doc/Doxyfile | 623 ++++++++++++----
doc/devel/02-dhcp.dox | 4 +-
doc/devel/mainpage.dox | 14 +-
doc/guide/bind10-guide.xml | 113 ++-
src/bin/auth/main.cc | 6 +-
src/bin/bind10/bind10_src.py.in | 81 +-
src/bin/bind10/tests/bind10_test.py.in | 765 ++++++++++++++++++-
src/bin/cfgmgr/b10-cfgmgr.py.in | 2 +-
src/bin/cmdctl/cmdctl.py.in | 2 +-
src/bin/ddns/ddns.py.in | 2 +-
src/bin/dhcp4/Makefile.am | 2 +
src/bin/dhcp4/config_parser.cc | 772 ++++++++++++++++++++
src/bin/{dhcp6 => dhcp4}/config_parser.h | 95 ++-
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 27 +-
src/bin/dhcp4/ctrl_dhcp4_srv.h | 4 +-
src/bin/dhcp4/dhcp4.dox | 68 ++
src/bin/dhcp4/dhcp4.spec | 80 +-
src/bin/dhcp4/dhcp4_messages.mes | 20 +
src/bin/dhcp4/dhcp4_srv.h | 2 +-
src/bin/dhcp4/main.cc | 8 +-
src/bin/dhcp4/tests/Makefile.am | 3 +
src/bin/dhcp4/tests/config_parser_unittest.cc | 294 ++++++++
src/bin/dhcp6/config_parser.cc | 41 +-
src/bin/dhcp6/config_parser.h | 25 +-
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 1 -
src/bin/dhcp6/ctrl_dhcp6_srv.h | 4 +-
src/bin/dhcp6/dhcp6.dox | 8 +-
src/bin/dhcp6/dhcp6_messages.mes | 27 +
src/bin/dhcp6/dhcp6_srv.cc | 332 ++++++---
src/bin/dhcp6/dhcp6_srv.h | 80 +-
src/bin/dhcp6/main.cc | 10 +-
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 354 +++++++--
src/bin/resolver/main.cc | 12 +-
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 | 50 +-
src/bin/xfrin/xfrin.py.in | 47 +-
src/bin/xfrin/xfrin_messages.mes | 9 +-
src/bin/xfrout/xfrout.py.in | 2 +-
src/bin/zonemgr/zonemgr.py.in | 2 +-
src/lib/datasrc/Makefile.am | 3 +-
src/lib/datasrc/{exceptions.h => client.cc} | 55 +-
src/lib/datasrc/client.h | 44 +-
src/lib/datasrc/database.cc | 55 +-
src/lib/datasrc/database.h | 28 +
src/lib/datasrc/datasrc_messages.mes | 10 +
src/lib/datasrc/master_loader_callbacks.cc | 20 +-
src/lib/datasrc/master_loader_callbacks.h | 2 +-
src/lib/datasrc/memory/memory_client.cc | 55 +-
src/lib/datasrc/memory/zone_data.cc | 22 +-
src/lib/datasrc/memory/zone_data.h | 14 +-
src/lib/datasrc/memory/zone_data_updater.cc | 2 +-
src/lib/datasrc/sqlite3_accessor.cc | 35 +-
src/lib/datasrc/sqlite3_accessor.h | 201 ++---
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/datasrc/tests/client_unittest.cc | 4 +
src/lib/datasrc/tests/database_unittest.cc | 61 +-
.../datasrc/tests/master_loader_callbacks_test.cc | 38 +-
.../datasrc/tests/memory/memory_client_unittest.cc | 19 +
.../datasrc/tests/memory/testdata/2503-test.zone | 13 +
src/lib/datasrc/tests/memory/testdata/Makefile.am | 1 +
src/lib/datasrc/tests/memory/zone_data_unittest.cc | 34 +-
.../datasrc/tests/memory/zone_finder_unittest.cc | 25 +
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 77 ++
src/lib/datasrc/tests/zone_loader_unittest.cc | 395 ++++++++++
src/lib/datasrc/zone_loader.cc | 132 ++++
src/lib/datasrc/zone_loader.h | 158 ++++
src/lib/dhcp/Makefile.am | 2 +
src/lib/dhcp/duid.cc | 10 +
src/lib/dhcp/duid.h | 6 +
src/lib/dhcp/libdhcp++.cc | 68 +-
src/lib/dhcp/libdhcp++.h | 13 +-
src/lib/dhcp/option.cc | 4 +
src/lib/dhcp/option.h | 19 +-
src/lib/dhcp/option4_addrlst.h | 4 +-
src/lib/dhcp/option6_addrlst.h | 4 +-
src/lib/dhcp/option6_ia.h | 9 +-
src/lib/dhcp/option_custom.cc | 334 ++++++++-
src/lib/dhcp/option_custom.h | 191 ++++-
src/lib/dhcp/option_data_types.cc | 41 +-
src/lib/dhcp/option_data_types.h | 44 +-
src/lib/dhcp/option_definition.cc | 95 ++-
src/lib/dhcp/option_definition.h | 2 +-
src/lib/dhcp/pkt4.cc | 57 +-
src/lib/dhcp/pkt4.h | 22 +-
src/lib/dhcp/pkt6.cc | 72 +-
src/lib/dhcp/pkt6.h | 57 +-
src/lib/dhcp/std_option_defs.h | 184 +++++
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/duid_unittest.cc | 12 +-
src/lib/dhcp/tests/libdhcp++_unittest.cc | 172 ++++-
src/lib/dhcp/tests/option6_ia_unittest.cc | 11 +-
src/lib/dhcp/tests/option_custom_unittest.cc | 553 +++++++++++++-
src/lib/dhcp/tests/option_data_types_unittest.cc | 491 +++++++++++++
src/lib/dhcp/tests/option_definition_unittest.cc | 3 +-
src/lib/dhcp/tests/option_unittest.cc | 23 +
src/lib/dhcp/tests/pkt4_unittest.cc | 11 +-
src/lib/dhcp/tests/pkt6_unittest.cc | 73 ++
src/lib/dhcpsrv/Makefile.am | 15 +
src/lib/dhcpsrv/addr_utilities.h | 8 +-
src/lib/dhcpsrv/alloc_engine.cc | 2 +-
src/lib/dhcpsrv/cfgmgr.cc | 8 +
src/lib/dhcpsrv/cfgmgr.h | 22 +-
src/lib/dhcpsrv/database_backends.dox | 2 +-
.../{nsas/nsas_log.cc => dhcpsrv/dhcpsrv_log.cc} | 8 +-
src/lib/{nsas/nsas_log.h => dhcpsrv/dhcpsrv_log.h} | 49 +-
src/lib/dhcpsrv/dhcpsrv_messages.mes | 102 +++
src/lib/dhcpsrv/lease_mgr.cc | 14 +-
src/lib/dhcpsrv/lease_mgr.h | 103 ++-
src/lib/dhcpsrv/lease_mgr_factory.cc | 51 +-
src/lib/dhcpsrv/lease_mgr_factory.h | 35 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 28 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 22 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 142 ++--
src/lib/dhcpsrv/mysql_lease_mgr.h | 40 +-
src/lib/dhcpsrv/subnet.h | 2 +-
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 85 ++-
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 24 +-
.../dhcpsrv/tests/memfile_lease_mgr_unittest.cc | 4 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 91 +--
src/lib/dhcpsrv/triplet.h | 2 +-
src/lib/dns/Makefile.am | 1 +
src/lib/dns/master_lexer.cc | 5 +
src/lib/dns/master_lexer.h | 8 +-
src/lib/dns/master_loader.cc | 367 ++++++++++
src/lib/dns/master_loader.h | 133 +++-
src/lib/dns/master_loader_callbacks.h | 28 +-
src/lib/dns/python/tests/tsigkey_python_test.py | 6 +
src/lib/dns/python/tsigkey_python.cc | 17 +-
src/lib/dns/rrset.h | 3 -
src/lib/dns/tests/Makefile.am | 1 +
src/lib/dns/tests/master_lexer_unittest.cc | 6 +
src/lib/dns/tests/master_loader_unittest.cc | 429 +++++++++++
src/lib/dns/tests/testdata/Makefile.am | 2 +
src/lib/dns/tests/testdata/broken.zone | 3 +
src/lib/dns/tests/testdata/example.org | 15 +
src/lib/dns/tests/tsigkey_unittest.cc | 9 +
src/lib/dns/tsigkey.cc | 12 +-
src/lib/dns/tsigkey.h | 22 +
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/logger_manager.cc | 6 +-
src/lib/log/logger_manager.h | 24 +-
src/lib/log/logger_manager_impl.cc | 80 +-
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/Makefile.am | 13 +
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} | 40 +-
src/lib/python/isc/config/cfgmgr.py | 9 +-
src/lib/python/isc/datasrc/client_inc.cc | 29 +
src/lib/python/isc/datasrc/client_python.cc | 33 +
src/lib/python/isc/datasrc/tests/datasrc_test.py | 49 +-
src/lib/python/isc/log/log.cc | 29 +-
src/lib/python/isc/log/tests/log_test.py | 22 +
tests/lettuce/features/xfrin_bind10.feature | 2 +-
163 files changed, 9569 insertions(+), 1363 deletions(-)
create mode 100644 src/bin/dhcp4/config_parser.cc
copy src/bin/{dhcp6 => dhcp4}/config_parser.h (62%)
create mode 100644 src/bin/dhcp4/dhcp4.dox
create mode 100644 src/bin/dhcp4/tests/config_parser_unittest.cc
copy src/lib/datasrc/{exceptions.h => client.cc} (50%)
create mode 100644 src/lib/datasrc/tests/memory/testdata/2503-test.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/std_option_defs.h
create mode 100644 src/lib/dhcp/tests/option_data_types_unittest.cc
copy src/lib/{nsas/nsas_log.cc => dhcpsrv/dhcpsrv_log.cc} (88%)
copy src/lib/{nsas/nsas_log.h => dhcpsrv/dhcpsrv_log.h} (52%)
create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
create mode 100644 src/lib/dns/master_loader.cc
create mode 100644 src/lib/dns/tests/master_loader_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/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%)
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 67fbc1b..eb15baf 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,6 +1,61 @@
+526. [bug] syephen
+ 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 assert failures
- in some cases.
+ 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
diff --git a/configure.ac b/configure.ac
index feeb9a9..3f7a269 100644
--- a/configure.ac
+++ b/configure.ac
@@ -903,11 +903,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
@@ -1375,6 +1376,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
diff --git a/doc/Doxyfile b/doc/Doxyfile
index fbd082f..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/lib/dhcpsrv \
- ../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 da1ac13..61a9ee4 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -758,7 +758,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>
@@ -1841,10 +1841,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>
@@ -2722,6 +2720,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
@@ -3397,16 +3404,94 @@ 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>
+ 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>
- 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:
+ Note: Although configuration is now accepted, it is not internally used
+ by they server yet. At this stage of development, the only way to alter
+ server configuration is to modify its 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";
@@ -3416,7 +3501,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>
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/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 36ad760..882653b 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
@@ -166,14 +166,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.
@@ -216,6 +216,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
@@ -332,6 +338,7 @@ class BoB:
"""
logger.info(BIND10_KILLING_ALL_PROCESSES)
self.__kill_children(True)
+ self.components = {}
def _read_bind10_config(self):
"""
@@ -400,7 +407,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 +415,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 +454,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 +471,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 +486,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 +503,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 +521,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
@@ -611,7 +633,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)
@@ -679,7 +700,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 +749,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 +783,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 +932,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 +1031,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
@@ -1173,7 +1198,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..409790c 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
@@ -366,6 +368,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)
@@ -461,6 +510,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 +555,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 +727,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 +1042,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 +1053,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 +1231,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 +1462,618 @@ 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:
+ raise Exception('Assume starting components has failed.')
+
+ 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 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):
"""
@@ -1563,6 +2305,25 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
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/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/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/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..c896591 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,6 +58,7 @@ 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/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
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
new file mode 100644
index 0000000..08d16f8
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.cc
@@ -0,0 +1,772 @@
+// 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/ccsession.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/config_parser.h>
+#include <dhcp4/dhcp4_log.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::data;
+using namespace isc::asiolink;
+
+namespace isc {
+namespace dhcp {
+
+/// @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 Dhcp4ConfigParser* 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 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;
+
+/// @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 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 Dhcp4ConfigParser {
+public:
+
+ /// @brief Constructor
+ ///
+ /// See \ref Dhcp4ConfigParser 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 Dhcp4ConfigParser 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 Dhcp4ConfigParser 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 Dhcp4ConfigParser* 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 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 Dhcp4ConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv4ConfigInherit page.
+class Uint32Parser : public Dhcp4ConfigParser {
+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) {
+ }
+
+ /// @brief builds parameter value
+ ///
+ /// Parses configuration entry and stores it in a storage. See
+ /// \ref setStorage() for details.
+ ///
+ /// @param value pointer to the content of parsed values
+ /// @throw BadValue if supplied value could not be base to uint32_t
+ virtual void build(ConstElementPtr value) {
+ 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);
+
+ (*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 Subnet4ConfigParser when constructing
+ /// the subnet.
+ virtual void commit() {
+ }
+
+ /// @brief factory that constructs Uint32Parser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* 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 Dhcp4ConfigParser.
+///
+/// For overview of usability of this generic purpose parser, see
+/// \ref dhcpv4ConfigInherit page.
+class StringParser : public Dhcp4ConfigParser {
+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) {
+ }
+
+ /// @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_, "\"");
+
+ (*storage_)[param_name_] = value_;
+ }
+
+ /// @brief does nothing
+ ///
+ /// This method is required for all parser. 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 factory that constructs StringParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* 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 Dhcp4ConfigParser {
+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 Dhcp4ConfigParser* 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 Dhcp4ConfigParser {
+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 Dhcp4ConfigError 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(Dhcp4ConfigError, "Failed to parse pool "
+ "definition: " << text_pool->stringValue());
+ }
+
+ Pool4Ptr pool(new Pool4(addr, len));
+ 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));
+
+ pools_->push_back(pool);
+ continue;
+ }
+
+ isc_throw(Dhcp4ConfigError, "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 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 factory that constructs PoolParser objects
+ ///
+ /// @param param_name name of the parameter to be parsed
+ static Dhcp4ConfigParser* 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_;
+};
+
+/// @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 Dhcp4ConfigParser {
+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));
+
+ // if this is an Uint32 parser, tell it to store the values
+ // in values_, rather than in global storage
+ boost::shared_ptr<Uint32Parser> uint_parser =
+ boost::dynamic_pointer_cast<Uint32Parser>(parser);
+ if (uint_parser) {
+ uint_parser->setStorage(&uint32_values_);
+ } else {
+
+ boost::shared_ptr<StringParser> string_parser =
+ boost::dynamic_pointer_cast<StringParser>(parser);
+ if (string_parser) {
+ string_parser->setStorage(&string_values_);
+ } else {
+
+ boost::shared_ptr<PoolParser> pool_parser =
+ boost::dynamic_pointer_cast<PoolParser>(parser);
+ if (pool_parser) {
+ pool_parser->setStorage(&pools_);
+ }
+ }
+ }
+
+ parser->build(param.second);
+ parsers_.push_back(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 Subnet4
+ /// objects. Subnet4 are then added to DHCP CfgMgr.
+ /// @throw Dhcp4ConfigError if there are any issues encountered during commit
+ void commit() {
+
+ StringStorage::const_iterator it = string_values_.find("subnet");
+ if (it == string_values_.end()) {
+ isc_throw(Dhcp4ConfigError,
+ "Mandatory subnet definition in subnet missing");
+ }
+ string subnet_txt = it->second;
+ boost::erase_all(subnet_txt, " ");
+ boost::erase_all(subnet_txt, "\t");
+
+ size_t pos = subnet_txt.find("/");
+ if (pos == string::npos) {
+ isc_throw(Dhcp4ConfigError,
+ "Invalid subnet syntax (prefix/len expected):" << it->second);
+ }
+ IOAddress addr(subnet_txt.substr(0, pos));
+ uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+
+ 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());
+
+ Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+
+ for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
+ subnet->addPool4(*it);
+ }
+
+ CfgMgr::instance().addSubnet4(subnet);
+ }
+
+private:
+
+ /// @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
+ Dhcp4ConfigParser* 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;
+
+ 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 Dhcp4ConfigError 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(Dhcp4ConfigError, "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_;
+
+ /// parsers are stored here
+ ParserCollection parsers_;
+};
+
+/// @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 Dhcp4ConfigParser {
+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 Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ return (new Subnets4ListConfigParser(param_name));
+ }
+
+ /// @brief collection of subnet parsers.
+ ParserCollection subnets_;
+};
+
+/// @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
+Dhcp4ConfigParser* 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["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());
+
+ ParserCollection parsers;
+ try {
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+
+ ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
+ parser->build(config_pair.second);
+ parsers.push_back(parser);
+ }
+ } catch (const isc::Exception& ex) {
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed:") + ex.what());
+ return (answer);
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ ConstElementPtr answer = isc::config::createAnswer(1,
+ string("Configuration parsing failed"));
+ }
+
+ try {
+ BOOST_FOREACH(ParserPtr parser, parsers) {
+ parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ ConstElementPtr answer = isc::config::createAnswer(2,
+ string("Configuration commit failed:") + ex.what());
+ return (answer);
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ ConstElementPtr answer = isc::config::createAnswer(2,
+ string("Configuration commit failed"));
+ }
+
+ LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
+
+ ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ return (answer);
+}
+
+}; // 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..2ee9cf6
--- /dev/null
+++ b/src/bin/dhcp4/config_parser.h
@@ -0,0 +1,167 @@
+// 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 <cc/data.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 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;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c Dhcpv4Srv object.
+class Dhcp4ConfigError : 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
+ Dhcp4ConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Base abstract class for all DHCPv4 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.
+/// @todo: Merge this class with DhcpConfigParser in src/bin/dhcp6
+class Dhcp4ConfigParser {
+ ///
+ /// \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.
+ Dhcp4ConfigParser(const Dhcp4ConfigParser& source);
+ Dhcp4ConfigParser& operator=(const Dhcp4ConfigParser& 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).
+ Dhcp4ConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~Dhcp4ConfigParser() {}
+ //@}
+
+ /// \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;
+};
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<Dhcp4ConfigParser> 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 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 - logical error (parsing was successful, but the values are invalid)
+///
+/// @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);
+
+}; // 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..eb5d482 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -22,8 +22,11 @@
#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 +49,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 +109,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 Dhcp4ConfigError& 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..d5066e8 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -4,9 +4,85 @@
"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": "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": ""
+ }
+ }
+ ]
+ }
}
],
"commands": [
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index 392b332..994f004 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,10 +26,30 @@ 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_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_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_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_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.
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index 48ccde6..db4b4bc 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -36,7 +36,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,
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..e601919 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
@@ -65,6 +67,7 @@ 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/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
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..a4ccabd
--- /dev/null
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -0,0 +1,294 @@
+// 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 <arpa/inet.h>
+#include <gtest/gtest.h>
+
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/config_parser.h>
+#include <config/ccsession.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#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 isc {
+namespace dhcp {
+extern Uint32Storage uint32_defaults;
+}
+}
+
+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) {
+ Uint32Storage::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() {
+ delete srv_;
+ };
+
+ 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 2 (values error)
+ // as the pool does not belong to that subnet
+ checkResult(status, 2);
+}
+
+// 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());
+}
+
+/// 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.
+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/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 1c55649..4c6fab1 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -129,7 +129,7 @@ public:
return (new DebugParser(param_name));
}
-protected:
+private:
/// name of the parsed parameter
std::string param_name_;
@@ -147,7 +147,7 @@ protected:
/// 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).
@@ -163,7 +163,7 @@ public:
/// @brief builds parameter value
///
/// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// \ref Uint32Parser::setStorage() for details.
///
/// @param value pointer to the content of parsed values
virtual void build(ConstElementPtr value) {
@@ -222,14 +222,14 @@ 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(Uint32Storage* storage) {
storage_ = storage;
}
-protected:
+private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
@@ -250,7 +250,7 @@ protected:
/// 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:
@@ -269,6 +269,7 @@ public:
virtual void build(ConstElementPtr value) {
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_;
@@ -293,14 +294,14 @@ 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(StringStorage* storage) {
storage_ = storage;
}
-protected:
+private:
/// pointer to the storage, where parsed value will be stored
StringStorage* storage_;
@@ -329,9 +330,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(BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -339,7 +341,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) {
@@ -361,7 +363,7 @@ public:
return (new InterfaceListConfigParser(param_name));
}
-protected:
+private:
/// contains list of network interfaces
vector<string> interfaces_;
};
@@ -390,6 +392,8 @@ 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 BadValue if storage was not specified (setStorage() not called)
void build(ConstElementPtr pools_list) {
// setStorage() should have been called before build
if (!pools_) {
@@ -442,7 +446,7 @@ 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));
@@ -459,7 +463,7 @@ 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) {
@@ -480,7 +484,7 @@ public:
return (new PoolParser(param_name));
}
-protected:
+private:
/// @brief pointer to the actual Pools storage
///
/// This is typically a storage somewhere in Subnet parser
@@ -1005,6 +1009,7 @@ private:
///
/// @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* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
@@ -1047,6 +1052,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
+ /// @throw Dhcp6ConfigError when requested parameter is not present
Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -1156,6 +1162,7 @@ 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;
@@ -1205,11 +1212,13 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
///
/// @param config_set a new configuration for DHCPv6 server
/// @return answer that contains result of reconfiguration
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
ConstElementPtr
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")
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index 9f7c3ae..ed44bb9 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -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 {
@@ -30,15 +32,22 @@ class Dhcpv6Srv;
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) {}
+ /// @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) {}
};
+/// @brief Base abstract class for all DHCPv6 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.
+/// @todo: Merge this class with Dhcp4ConfigParser in src/bin/dhcp4
class DhcpConfigParser {
///
/// \name Constructors and Destructor
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 5d4d990..8611365 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -17,7 +17,6 @@
#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 <dhcp6/config_parser.h>
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..5350fd8 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
@@ -49,7 +49,7 @@
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
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6b82344..6ab42b3 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.
@@ -81,6 +86,13 @@ 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_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_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.
@@ -189,3 +201,18 @@ 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.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index 02c0dd0..2482833 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -24,6 +24,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <dhcp/pkt6.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
@@ -126,50 +127,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) {
@@ -195,9 +203,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);
@@ -348,13 +353,77 @@ 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);
+}
+
+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;
+ }
- // @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);
+ Option::OptionCollection server_ids = pkt->getOptions(D6O_SERVERID);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_ids.size() > 0) {
+ isc_throw(RFCViolation, "Exactly 1 server-id option 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) {
@@ -370,7 +439,7 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// 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
@@ -378,12 +447,13 @@ 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);
+ } 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
@@ -397,6 +467,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
@@ -409,7 +483,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);
@@ -422,14 +496,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.
@@ -471,6 +549,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) {
@@ -512,8 +592,111 @@ 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_NoAddrsAvail,
+ "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.
+
+ // 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;
+ }
+ }
+
+
+
+}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
+ sanityCheck(solicit, MANDATORY, FORBIDDEN);
+
Pkt6Ptr advertise(new Pkt6(DHCPV6_ADVERTISE, solicit->getTransid()));
copyDefaultOptions(solicit, advertise);
@@ -526,6 +709,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);
@@ -538,8 +724,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;
}
@@ -573,48 +768,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);
-}
-
};
};
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index de9ee36..b6b0306 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -30,6 +30,23 @@
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) {}
+};
+
+
+
/// @brief DHCPv6 server service.
///
/// This class represents DHCPv6 server. It contains all
@@ -45,6 +62,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;
@@ -83,24 +106,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 +205,24 @@ 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. 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
+ OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ Pkt6Ptr question, 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 +253,24 @@ 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 Sets server-identifier.
///
/// This method attempts to set server-identifier DUID. It loads it
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/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 335fb7b..de0dc28 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -19,6 +19,7 @@
#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>
@@ -57,8 +58,10 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
+ using Dhcpv6Srv::processRenew;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
+ using Dhcpv6Srv::sanityCheck;
};
class Dhcpv6SrvTest : public ::testing::Test {
@@ -139,6 +142,33 @@ public:
return (addr);
}
+ // Checks that server rejected IA_NA, i.e. that it has no addresses and
+ // that expected status code really appears there.
+ // 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 checkRejectedIA_NA(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));
+ 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));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -617,7 +647,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.
@@ -651,6 +680,9 @@ 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);
@@ -674,7 +706,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// 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
@@ -709,6 +741,11 @@ 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);
@@ -749,50 +786,207 @@ 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) {
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
-TEST_F(Dhcpv6SrvTest, serverReceivedPacketName) {
- // Check all possible packet types
- for (int itype = 0; itype < 256; ++itype) {
- uint8_t type = itype;
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
- switch (type) {
- case DHCPV6_CONFIRM:
- EXPECT_STREQ("CONFIRM", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
- case DHCPV6_DECLINE:
- EXPECT_STREQ("DECLINE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // 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);
- case DHCPV6_INFORMATION_REQUEST:
- EXPECT_STREQ("INFORMATION_REQUEST",
- 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_REBIND:
- EXPECT_STREQ("REBIND", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Server-id is mandatory in RENEW
+ req->addOption(srv->getServerID());
- case DHCPV6_RELEASE:
- EXPECT_STREQ("RELEASE", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv->processRenew(req);
- case DHCPV6_RENEW:
- EXPECT_STREQ("RENEW", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
- case DHCPV6_REQUEST:
- EXPECT_STREQ("REQUEST", Dhcpv6Srv::serverReceivedPacketName(type));
- break;
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
- case DHCPV6_SOLICIT:
- EXPECT_STREQ("SOLICIT", 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());
- default:
- EXPECT_STREQ("UNKNOWN", Dhcpv6Srv::serverReceivedPacketName(type));
- }
- }
+ // Check that we've got the address we requested
+ checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
+
+ // Check DUIDs
+ checkServerId(reply, srv->getServerID());
+ checkClientId(reply, clientid);
+
+ // 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) {
+
+ boost::scoped_ptr<NakedDhcpv6Srv> srv;
+ ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(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);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ // 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);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ // 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);
+ checkRejectedIA_NA(ia, STATUS_NoAddrsAvail);
+
+ 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 if the status code option is generated properly.
@@ -801,16 +995,27 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(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");
-
- EXPECT_TRUE(status->getData() == exp);
+ // 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 selectSubnet() method works as expected.
-TEST_F(Dhcpv6SrvTest, SelectSubnet) {
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv6SrvTest, sanityCheck) {
boost::scoped_ptr<NakedDhcpv6Srv> srv;
ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
@@ -818,18 +1023,63 @@ TEST_F(Dhcpv6SrvTest, SelectSubnet) {
// check that the packets originating from local addresses can be
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
- // packets originating from subnet A will select subnet A
- pkt->setRemoteAddr(IOAddress("2001:db8:1::6789"));
- EXPECT_EQ(subnet_, srv->selectSubnet(pkt));
+ // 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);
+
+ // again we have only one client-id
+
+ // 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);
- // packets from a subnet that is not supported will not get
- // a subnet
- pkt->setRemoteAddr(IOAddress("3000::faf"));
- EXPECT_FALSE(srv->selectSubnet(pkt));
- /// @todo: expand this test once support for relays is implemented
}
} // end of anonymous namespace
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/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..b7fe056 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
@@ -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 8e92db6..da0f207 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
@@ -69,7 +70,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.
@@ -1179,7 +1183,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):
@@ -1240,20 +1244,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
@@ -1308,6 +1324,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,
@@ -1468,7 +1485,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:
@@ -1491,7 +1508,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..837cafa 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -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.
@@ -160,6 +160,13 @@ run time: Time (in seconds) the complete axfr took
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
exception message is printed in the log message.
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index 835696e..f869955 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -38,7 +38,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/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index 86f89fa..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
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index f4e768a..a4edd4e 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -31,13 +31,14 @@ 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 += 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..919e9ab
--- /dev/null
+++ b/src/lib/datasrc/client.cc
@@ -0,0 +1,48 @@
+// 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 addZone");
+}
+
+} // end namespace datasrc
+} // end namespace isc
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 3756a68..8739489 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,36 @@ 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 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& name);
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index fbada44..b65bc08 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -43,6 +43,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 +116,18 @@ DatabaseClient::findZone(const Name& name) const {
return (FindResult(result::NOTFOUND, ZoneFinderPtr()));
}
+bool
+DatabaseClient::createZone(const Name& name) {
+ TransactionHolder transaction(*accessor_);
+ std::pair<bool, int> zone(accessor_->getZone(name.toText()));
+ if (zone.first) {
+ return (false);
+ }
+ accessor_->addZone(name.toText());
+ transaction.commit();
+ return (true);
+}
+
DatabaseClient::Finder::Finder(boost::shared_ptr<DatabaseAccessor> accessor,
int zone_id, const isc::dns::Name& origin) :
accessor_(accessor),
@@ -1349,11 +1397,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());
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 320f327..9db8a8f 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -177,6 +177,24 @@ 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 This holds the internal context of ZoneIterator for databases
///
/// While the ZoneIterator implementation from DatabaseClient does all the
@@ -1373,6 +1391,16 @@ 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& 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 e7cb9d3..e9b4c90 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -216,6 +216,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,
diff --git a/src/lib/datasrc/master_loader_callbacks.cc b/src/lib/datasrc/master_loader_callbacks.cc
index 54e4d0e..04b8940 100644
--- a/src/lib/datasrc/master_loader_callbacks.cc
+++ b/src/lib/datasrc/master_loader_callbacks.cc
@@ -48,6 +48,19 @@ logWarning(const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
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
@@ -61,12 +74,9 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
rrclass, _1, _2, _3)));
}
-isc::dns::AddRRsetCallback
+isc::dns::AddRRCallback
createMasterLoaderAddCallback(ZoneUpdater& updater) {
- return (boost::bind(&ZoneUpdater::addRRset, &updater,
- // The callback provides a shared pointer, we
- // need the object. This bind unpacks the object.
- boost::bind(&isc::dns::RRsetPtr::operator*, _1)));
+ 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
index c258303..ae827c9 100644
--- a/src/lib/datasrc/master_loader_callbacks.h
+++ b/src/lib/datasrc/master_loader_callbacks.h
@@ -58,7 +58,7 @@ createMasterLoaderCallbacks(const isc::dns::Name& name,
/// \param updater The zone updater to use.
/// \return The callback to be passed to MasterLoader.
/// \throw std::bad_alloc when allocation fails.
-isc::dns::AddRRsetCallback
+isc::dns::AddRRCallback
createMasterLoaderAddCallback(ZoneUpdater& updater);
}
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/zone_data.cc b/src/lib/datasrc/memory/zone_data.cc
index 1bf9c9c..cc31419 100644
--- a/src/lib/datasrc/memory/zone_data.cc
+++ b/src/lib/datasrc/memory/zone_data.cc
@@ -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_updater.cc b/src/lib/datasrc/memory/zone_data_updater.cc
index 7605644..51ec03c 100644
--- a/src/lib/datasrc/memory/zone_data_updater.cc
+++ b/src/lib/datasrc/memory/zone_data_updater.cc
@@ -233,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 {
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 68d6554..4ef49d0 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -78,7 +78,8 @@ enum StatementID {
ADD_NSEC3_RECORD = 19,
DEL_ZONE_NSEC3_RECORDS = 20,
DEL_NSEC3_RECORD = 21,
- NUM_STATEMENTS = 22
+ ADD_ZONE = 22,
+ NUM_STATEMENTS = 23
};
const char* const text_statements[NUM_STATEMENTS] = {
@@ -161,7 +162,10 @@ 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
};
struct SQLite3Parameters {
@@ -612,6 +616,33 @@ 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);
+}
+
namespace {
// Conversion to plain char
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index a8112d4..c5773d0 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,83 @@ 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);
+
+ /// \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 +162,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 d3ef3bf..61858bd 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -60,6 +60,7 @@ 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)
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..174da04 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -274,6 +274,11 @@ public:
}
}
+ virtual int addZone(const std::string&) {
+ isc_throw(isc::NotImplemented,
+ "This database datasource can't add zones");
+ }
+
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,6 +334,7 @@ public:
isc_throw(isc::NotImplemented,
"This test database knows nothing about NSEC3 nor order");
}
+
private:
const std::string database_name_;
@@ -1780,7 +1786,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")));
@@ -4092,4 +4098,57 @@ 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);
+
+ // The mock implementation does not do createZone,
+ // in which case it should throw NotImplemented (from
+ // the base class)
+ if (this->is_mock_) {
+ ASSERT_THROW(this->client_->createZone(new_name), isc::NotImplemented);
+ } else {
+ // But in the real case, 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
+ ASSERT_FALSE(this->client_->createZone(new_name));
+ }
+}
+
+TYPED_TEST(DatabaseClientTest, createZoneRollbackOnLocked) {
+ // skip test for mock
+ if (this->is_mock_) {
+ return;
+ }
+
+ const Name new_name("example.com");
+ isc::datasrc::ZoneUpdaterPtr updater =
+ this->client_->getUpdater(this->zname_, true);
+ 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();
+ ASSERT_TRUE(this->client_->createZone(new_name));
+}
+
+TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
+ // skip test for mock
+ if (this->is_mock_) {
+ return;
+ }
+
+ const Name new_name("example.com");
+ ASSERT_FALSE(this->client_->createZone(this->zname_));
+ // createZone started a transaction, but since it failed,
+ // it should have been rolled back, and the next attempt should succeed
+ ASSERT_TRUE(this->client_->createZone(new_name));
+}
+
}
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
index f814288..19ec4d2 100644
--- a/src/lib/datasrc/tests/master_loader_callbacks_test.cc
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -18,6 +18,9 @@
#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>
@@ -40,8 +43,21 @@ public:
// the correct ones, according to a predefined set in a list.
virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
ASSERT_FALSE(expected_rrsets_.empty());
- // In our tests, pointer equality is enough.
- EXPECT_EQ(expected_rrsets_.front().get(), &rrset);
+
+ // 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();
}
@@ -67,14 +83,22 @@ protected:
isc::dns::RRClass::IN(), &ok_))
{}
// Generate a new RRset, put it to the updater and return it.
- isc::dns::RRsetPtr generateRRset() {
+ 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);
- return (result);
+
+ callback(result->getName(), result->getClass(), result->getType(),
+ result->getTTL(), data);
}
// An updater to be passed to the context
MockUpdater updater_;
@@ -112,11 +136,11 @@ TEST_F(MasterLoaderCallbackTest, callbacks) {
// Try adding some RRsets.
TEST_F(MasterLoaderCallbackTest, addRRset) {
- isc::dns::AddRRsetCallback
+ isc::dns::AddRRCallback
callback(createMasterLoaderAddCallback(updater_));
// Put some of them in.
- EXPECT_NO_THROW(callback(generateRRset()));
- EXPECT_NO_THROW(callback(generateRRset()));
+ 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/memory_client_unittest.cc b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
index 2a9a35d..0a03645 100644
--- a/src/lib/datasrc/tests/memory/memory_client_unittest.cc
+++ b/src/lib/datasrc/tests/memory/memory_client_unittest.cc
@@ -690,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/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/Makefile.am b/src/lib/datasrc/tests/memory/testdata/Makefile.am
index d99872c..ef5f08b 100644
--- a/src/lib/datasrc/tests/memory/testdata/Makefile.am
+++ b/src/lib/datasrc/tests/memory/testdata/Makefile.am
@@ -30,4 +30,5 @@ 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_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_finder_unittest.cc b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
index 510e39f..42667da 100644
--- a/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
+++ b/src/lib/datasrc/tests/memory/zone_finder_unittest.cc
@@ -1587,4 +1587,29 @@ TEST_F(InMemoryZoneFinderNSEC3Test, RRSIGOnly) {
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/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 100a0dd..8dff6d5 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -667,6 +667,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 +1596,23 @@ 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);
+}
+
} // end anonymous namespace
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..b19a843
--- /dev/null
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -0,0 +1,395 @@
+// 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 <string>
+#include <vector>
+
+using isc::dns::RRClass;
+using isc::dns::Name;
+using isc::dns::RRType;
+using isc::dns::ConstRRsetPtr;
+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_;
+ // We store string representations of the RRsets. This is simpler than
+ // copying them and we can't really put them into shared pointers, because
+ // we get them as references.
+ vector<string> rrsets_;
+ 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_;
+};
+
+// 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) :
+ client_(client),
+ finder_(client_->rrclass_)
+ {}
+ virtual ZoneFinder& getFinder() {
+ return (finder_);
+ }
+ virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
+ if (client_->commit_called_) {
+ isc_throw(DataSourceError, "Add after commit");
+ }
+ client_->rrsets_.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_;
+ class Finder : public ZoneFinder {
+ public:
+ Finder(const RRClass& rrclass) :
+ class_(rrclass)
+ {}
+ virtual RRClass getClass() const {
+ return (class_);
+ }
+ virtual Name getOrigin() const {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ virtual shared_ptr<Context> find(const Name&, const RRType&,
+ const FindOptions)
+ {
+ isc_throw(isc::NotImplemented, "Method not used in tests");
+ }
+ 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_;
+ } 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))));
+}
+
+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, load uses an urelated implementation. In the long term,
+ // the method will probably be deprecated. At that time, we should
+ // probably prepare the data in some other way (using sqlite3 or
+ // something). This is simpler for now.
+ 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]);
+ // 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());
+ // Ensure known order.
+ std::sort(destination_client_.rrsets_.begin(),
+ destination_client_.rrsets_.end());
+ EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+ destination_client_.rrsets_.front());
+ EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+ destination_client_.rrsets_.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_);
+
+ // 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_.rrsets_.begin(),
+ destination_client_.rrsets_.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_.rrsets_[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_.rrsets_[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");
+ // 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());
+ // Ensure known order.
+ std::sort(destination_client_.rrsets_.begin(),
+ destination_client_.rrsets_.end());
+ EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
+ destination_client_.rrsets_.front());
+ EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
+ destination_client_.rrsets_.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");
+
+ // 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_);
+
+ // 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);
+}
+
+// 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_.rrsets_.begin(),
+ destination_client_.rrsets_.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_.rrsets_[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_.rrsets_[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_);
+}
+
+}
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
new file mode 100644
index 0000000..01f216e
--- /dev/null
+++ b/src/lib/datasrc/zone_loader.cc
@@ -0,0 +1,132 @@
+// 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 <dns/rrset.h>
+
+using isc::dns::Name;
+using isc::dns::ConstRRsetPtr;
+using isc::dns::MasterLoader;
+
+namespace isc {
+namespace datasrc {
+
+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)
+{
+ // 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");
+ }
+}
+
+ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
+ const char* filename) :
+ updater_(destination.getUpdater(zone_name, true, false)),
+ complete_(false),
+ loaded_ok_(true)
+{
+ 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_),
+ createMasterLoaderAddCallback(*updater_)));
+ }
+}
+
+namespace {
+
+// Copy up to limit RRsets from source to destination
+bool
+copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
+ size_t limit)
+{
+ 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;
+ }
+ return (false); // Not yet, there may be more
+}
+
+} // end unnamed namespace
+
+bool
+ZoneLoader::loadIncremental(size_t limit) {
+ if (complete_) {
+ isc_throw(isc::InvalidOperation,
+ "Loading has been completed previously");
+ }
+
+ if (iterator_ == ZoneIteratorPtr()) {
+ assert(loader_.get() != NULL);
+ 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);
+ }
+
+ if (complete_) {
+ updater_->commit();
+ }
+ return (complete_);
+}
+
+} // 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..2946116
--- /dev/null
+++ b/src/lib/datasrc/zone_loader.h
@@ -0,0 +1,158 @@
+// 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 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.
+ 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).
+ ///
+ /// \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.
+ /// \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);
+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_;
+};
+
+}
+}
+
+#endif
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 85048b0..f41ce53 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -33,10 +33,12 @@ libb10_dhcp___la_SOURCES += option6_int_array.h
libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.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
diff --git a/src/lib/dhcp/duid.cc b/src/lib/dhcp/duid.cc
index 91efe94..f1c8866 100644
--- a/src/lib/dhcp/duid.cc
+++ b/src/lib/dhcp/duid.cc
@@ -95,6 +95,16 @@ const std::vector<uint8_t> ClientId::getClientId() const {
return (duid_);
}
+// 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_);
diff --git a/src/lib/dhcp/duid.h b/src/lib/dhcp/duid.h
index 1b75f73..a4e32b3 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -107,6 +107,9 @@ public:
/// @brief Returns reference to the client-id data
const std::vector<uint8_t> getClientId() 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;
@@ -114,6 +117,9 @@ public:
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/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index a997d31..2773d09 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int_array.h>
#include <dhcp/option_definition.h>
+#include <dhcp/std_option_defs.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
@@ -45,7 +46,7 @@ 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:
initStdOptionDefs4();
@@ -60,6 +61,17 @@ LibDHCP::getOptionDefs(Option::Universe u) {
}
}
+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());
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
@@ -254,52 +266,16 @@ void
LibDHCP::initStdOptionDefs6() {
v6option_defs_.clear();
- struct OptionParams {
- std::string name;
- uint16_t code;
- OptionDataType type;
- bool array;
- };
- OptionParams params[] = {
- { "CLIENTID", D6O_CLIENTID, OPT_BINARY_TYPE, false },
- { "SERVERID", D6O_SERVERID, OPT_BINARY_TYPE, false },
- { "IA_NA", D6O_IA_NA, OPT_RECORD_TYPE, false },
- { "IAADDR", D6O_IAADDR, OPT_RECORD_TYPE, false },
- { "ORO", D6O_ORO, OPT_UINT16_TYPE, true },
- { "ELAPSED_TIME", D6O_ELAPSED_TIME, OPT_UINT16_TYPE, false },
- { "STATUS_CODE", D6O_STATUS_CODE, OPT_RECORD_TYPE, false },
- { "RAPID_COMMIT", D6O_RAPID_COMMIT, OPT_EMPTY_TYPE, false },
- { "DNS_SERVERS", D6O_NAME_SERVERS, OPT_IPV6_ADDRESS_TYPE, true },
- { "IA_PD", D6O_IA_PD, OPT_RECORD_TYPE, false }
- };
- 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:
- case D6O_IA_PD:
- for (int j = 0; j < 3; ++j) {
- definition->addRecordField(OPT_UINT32_TYPE);
- }
- break;
- case D6O_IAADDR:
- definition->addRecordField(OPT_IPV6_ADDRESS_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- definition->addRecordField(OPT_UINT32_TYPE);
- break;
- case D6O_STATUS_CODE:
- definition->addRecordField(OPT_UINT16_TYPE);
- definition->addRecordField(OPT_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) {
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index cab7928..e855070 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -42,7 +42,18 @@ public:
/// @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 Factory function to create instance of option.
///
diff --git a/src/lib/dhcp/option.cc b/src/lib/dhcp/option.cc
index 4638025..2a53f0f 100644
--- a/src/lib/dhcp/option.cc
+++ b/src/lib/dhcp/option.cc
@@ -314,6 +314,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 a6b0622..3c7799f 100644
--- a/src/lib/dhcp/option.h
+++ b/src/lib/dhcp/option.h
@@ -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.
///
@@ -197,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)
@@ -219,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.
///
@@ -303,6 +303,19 @@ 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.
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.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.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/option_custom.cc b/src/lib/dhcp/option_custom.cc
index 8b3ef11..0c3cb81 100644
--- a/src/lib/dhcp/option_custom.cc
+++ b/src/lib/dhcp/option_custom.cc
@@ -21,11 +21,28 @@ 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) {
- createBuffers();
+ // 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,
@@ -34,25 +51,156 @@ OptionCustom::OptionCustom(const OptionDefinition& def,
OptionBufferConstIter last)
: Option(u, def.getCode(), first, last),
definition_(def) {
- createBuffers();
+ createBuffers(data_);
+}
+
+void
+OptionCustom::addArrayDataField(const asiolink::IOAddress& address) {
+ checkArrayType();
+
+ if ((address.getFamily() == AF_INET &&
+ definition_.getType() != OPT_IPV4_ADDRESS_TYPE) ||
+ (address.getFamily() == AF_INET6 &&
+ 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 rangex.");
+ << " 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::iterator data = data_.begin();
+ OptionBuffer::const_iterator data = data_buf.begin();
OptionDataType data_type = definition_.getType();
if (data_type == OPT_RECORD_TYPE) {
@@ -68,17 +216,31 @@ OptionCustom::createBuffers() {
// For fixed-size data type such as boolean, integer, even
// IP address we can use the utility function to get the required
// buffer size.
- int data_size = OptionDataTypeUtil::getDataTypeLen(*field);
-
- // For variable size types (such as string) the function above
- // will return 0 so we need to do a runtime check. Since variable
- // length data fields may be laid only at the end of an option we
- // consume the rest of this option. Note that validate() function
- // in OptionDefinition object should have checked whether the
- // data fields layout is correct (that the variable string fields
- // are laid at the end).
+ 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) {
- data_size = std::distance(data, data_.end());
+ // 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
@@ -90,7 +252,7 @@ OptionCustom::createBuffers() {
} 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_.end()) < data_size) {
+ if (std::distance(data, data_buf.end()) < data_size) {
isc_throw(OutOfRange, "option buffer truncated");
}
}
@@ -105,39 +267,64 @@ OptionCustom::createBuffers() {
// 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.
- int data_size = OptionDataTypeUtil::getDataTypeLen(data_type);
+ 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_.end()) < data_size) {
+ 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()) {
- // 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 equal chunks of data and store as 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.
- do {
+ 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;
- } while (std::distance(data, data_.end()) >= 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) {
- data_size = std::distance(data, data_.end());
+ // 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));
@@ -185,6 +372,9 @@ OptionCustom::dataFieldToText(const OptionDataType data_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;
@@ -252,8 +442,31 @@ OptionCustom::readAddress(const uint32_t index) const {
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());
+ << " 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.getFamily() == AF_INET &&
+ buffers_[index].size() != V4ADDRESS_LEN) ||
+ (address.getFamily() == AF_INET6 &&
+ 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&
@@ -262,12 +475,50 @@ OptionCustom::readBinary(const uint32_t index) const {
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);
@@ -275,11 +526,26 @@ OptionCustom::readString(const uint32_t index) const {
}
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();
+ createBuffers(data_);
}
uint16_t
@@ -311,7 +577,7 @@ void OptionCustom::setData(const OptionBufferConstIter first,
// Chop the data_ buffer into set of buffers that represent
// option fields data.
- createBuffers();
+ createBuffers(data_);
}
std::string OptionCustom::toText(int indent) {
diff --git a/src/lib/dhcp/option_custom.h b/src/lib/dhcp/option_custom.h
index ad248c5..0ee4688 100644
--- a/src/lib/dhcp/option_custom.h
+++ b/src/lib/dhcp/option_custom.h
@@ -40,6 +40,17 @@ 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.
@@ -76,6 +87,37 @@ public:
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.
@@ -87,7 +129,17 @@ public:
///
/// @return IP address read from a buffer.
/// @throw isc::OutOfRange if index is out of range.
- asiolink::IOAddress readAddress(const uint32_t index) const;
+ 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.
///
@@ -95,7 +147,13 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read buffer holding binary data.
- const OptionBuffer& readBinary(const uint32_t index) const;
+ 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.
///
@@ -103,7 +161,33 @@ public:
///
/// @throw isc::OutOfRange if index is out of range.
/// @return read boolean value.
- bool readBoolean(const uint32_t index) const;
+ 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.
///
@@ -111,38 +195,15 @@ public:
/// @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) const {
+ T readInteger(const uint32_t index = 0) const {
+ // Check that the index is not out of range.
checkIndex(index);
-
- // Check that the requested return type is a supported integer.
- if (!OptionDataTypeTraits<T>::integer_type) {
- isc_throw(isc::dhcp::InvalidDataType, "specified data type to be returned"
- " by readInteger is not 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];
- }
-
- // Requested data type must match the data type in a record.
- if (OptionDataTypeTraits<T>::type != data_type) {
- isc_throw(isc::dhcp::InvalidDataType,
- "unable to read option field with index " << index
- << " as integer value. The field's data type"
- << data_type << " does not match the integer type"
- << "returned by the readInteger function.");
- }
+ // 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);
@@ -150,13 +211,43 @@ public:
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) const;
+ 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.
///
@@ -201,6 +292,33 @@ protected:
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.
@@ -208,9 +326,14 @@ private:
/// @throw isc::OutOfRange if index is out of range.
void checkIndex(const uint32_t index) const;
- /// @brief Create collection of buffers representing data field values.
+ /// @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.
diff --git a/src/lib/dhcp/option_data_types.cc b/src/lib/dhcp/option_data_types.cc
index 46aa663..0c512d7 100644
--- a/src/lib/dhcp/option_data_types.cc
+++ b/src/lib/dhcp/option_data_types.cc
@@ -13,6 +13,8 @@
// 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 {
@@ -207,9 +209,46 @@ OptionDataTypeUtil::writeBool(const bool value,
}
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.size() > 0) {
+ if (!buf.empty()) {
value.insert(value.end(), buf.begin(), buf.end());
}
return (value);
diff --git a/src/lib/dhcp/option_data_types.h b/src/lib/dhcp/option_data_types.h
index afc6d93..ff5789d 100644
--- a/src/lib/dhcp/option_data_types.h
+++ b/src/lib/dhcp/option_data_types.h
@@ -225,11 +225,13 @@ public:
/// @return data type size or zero for variable length types.
static int getDataTypeLen(const OptionDataType data_type);
- /// @brief Read IPv4 or IPv6 addres from a buffer.
+ /// @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);
@@ -252,6 +254,9 @@ public:
/// @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);
@@ -268,6 +273,9 @@ public:
///
/// @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) {
@@ -276,7 +284,12 @@ public:
" by readInteger is unsupported integer type");
}
- assert(buf.size() == OptionDataTypeTraits<T>::len);
+ 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:
@@ -332,6 +345,33 @@ public:
}
}
+ /// @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.
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index 58d0c4b..2248bd7 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -19,6 +19,7 @@
#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 <util/encode/hex.h>
@@ -78,53 +79,81 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
validate();
-
+
try {
- if (type_ == OPT_BINARY_TYPE) {
+ switch(type_) {
+ case OPT_EMPTY_TYPE:
+ return (factoryEmpty(u, type));
+
+ case OPT_BINARY_TYPE:
return (factoryGeneric(u, type, begin, end));
- } else if (type_ == OPT_IPV6_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList6(type, begin, end));
+ case OPT_UINT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<uint8_t>(u, type, begin, end));
- } else if (type_ == OPT_IPV4_ADDRESS_TYPE && array_type_) {
- return (factoryAddrList4(type, begin, end));
+ case OPT_INT8_TYPE:
+ return (array_type_ ? factoryGeneric(u, type, begin, end) :
+ factoryInteger<int8_t>(u, type, begin, end));
- } else if (type_ == OPT_EMPTY_TYPE) {
- return (factoryEmpty(u, type));
+ case OPT_UINT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<uint16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IA_NA &&
- haveIA6Format()) {
- return (factoryIA6(type, begin, end));
+ case OPT_INT16_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint16_t>(type, begin, end) :
+ factoryInteger<int16_t>(u, type, begin, end));
- } else if (u == Option::V6 &&
- code_ == D6O_IAADDR &&
- haveIAAddr6Format()) {
- return (factoryIAAddr6(type, begin, end));
+ case OPT_UINT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<uint32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT8_TYPE) {
- if (array_type_) {
- return (factoryGeneric(u, type, begin, end));
- } else {
- return (factoryInteger<uint8_t>(u, type, begin, end));
- }
+ case OPT_INT32_TYPE:
+ return (array_type_ ? factoryIntegerArray<uint32_t>(type, begin, end) :
+ factoryInteger<int32_t>(u, type, begin, end));
- } else if (type_ == OPT_UINT16_TYPE) {
+ 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 (factoryIntegerArray<uint16_t>(type, begin, end));
- } else {
- return (factoryInteger<uint16_t>(u, type, begin, end));
+ return (factoryAddrList4(type, begin, end));
}
+ break;
- } else if (type_ == OPT_UINT32_TYPE) {
+ case OPT_IPV6_ADDRESS_TYPE:
+ // Handle array type only here (see comments for
+ // OPT_IPV4_ADDRESS_TYPE case).
if (array_type_) {
- return (factoryIntegerArray<uint32_t>(type, begin, end));
- } else {
- return (factoryInteger<uint32_t>(u, type, begin, end));
+ 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));
+ }
}
-
}
- return (factoryGeneric(u, type, begin, end));
+ return (OptionPtr(new OptionCustom(*this, u, OptionBuffer(begin, end))));
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
@@ -144,7 +173,7 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
OptionBuffer buf;
if (!array_type_ && type_ != OPT_RECORD_TYPE) {
- if (values.size() == 0) {
+ if (values.empty()) {
isc_throw(InvalidOptionValue, "no option value specified");
}
writeToBuffer(values[0], type_, buf);
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 3d48ef2..ca40428 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -246,7 +246,7 @@ public:
/// @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) const;
+ const OptionBuffer& buf = OptionBuffer()) const;
/// @brief Option factory.
///
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 5be8211..15c885c 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
@@ -18,6 +18,7 @@
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
+#include <algorithm>
#include <iostream>
#include <sstream>
@@ -78,6 +79,9 @@ Pkt4::Pkt4(const uint8_t* data, size_t 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);
@@ -158,7 +162,7 @@ Pkt4::unpack() {
// 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,8 +182,8 @@ 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();
}
@@ -187,8 +191,9 @@ void Pkt4::check() {
boost::shared_ptr<Option> typeOpt = getOption(DHO_DHCP_MESSAGE_TYPE);
if (typeOpt) {
uint8_t msg_type = typeOpt->getUint8();
- if (msg_type>DHCPLEASEACTIVE) {
- isc_throw(BadValue, "Invalid DHCP message type received:" << msg_type);
+ if (msg_type > DHCPLEASEACTIVE) {
+ isc_throw(BadValue, "Invalid DHCP message type received: "
+ << msg_type);
}
msg_type_ = msg_type;
@@ -221,21 +226,21 @@ 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);
+ std::copy(&mac_addr[0], &mac_addr[hlen], &chaddr_[0]);
+ std::fill(&chaddr_[hlen], &chaddr_[MAX_CHADDR_LEN], 0);
}
void
@@ -243,11 +248,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 +264,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 +285,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
case DHCPINFORM:
case DHCPLEASEQUERY:
return (BOOTREQUEST);
+
case DHCPACK:
case DHCPNAK:
case DHCPOFFER:
@@ -279,6 +293,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
case DHCPLEASEUNKNOWN:
case DHCPLEASEACTIVE:
return (BOOTREPLY);
+
default:
isc_throw(OutOfRange, "Invalid message type: "
<< static_cast<int>(dhcpType) );
@@ -287,7 +302,7 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
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.");
@@ -298,7 +313,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
boost::shared_ptr<isc::dhcp::Option>
Pkt4::getOption(uint8_t type) {
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
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index e09069c..5139458 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -89,7 +89,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();
@@ -262,16 +262,16 @@ 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);
/// Returns htype field
///
@@ -367,7 +367,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 +381,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 +393,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 +403,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.
///
@@ -519,13 +519,13 @@ protected:
std::vector<uint8_t> data_;
/// message type (e.g. 1=DHCPDISCOVER)
- /// TODO: this will eventually be replaced with DHCP Message Type
+ /// @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'
/// behavior must be taken into consideration before making
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..d66dcf8
--- /dev/null
+++ b/src/lib/dhcp/std_option_defs.h
@@ -0,0 +1,184 @@
+// 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...) static 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
+ OptionDataType* records; // record fields
+ size_t records_size; // number of fields in a record
+};
+
+// client-fqdn
+RECORD_DECL(clientFqdnRecords, OPT_UINT8_TYPE, OPT_FQDN_TYPE);
+// geoconf-civic
+RECORD_DECL(geoconfCivicRecords, OPT_UINT8_TYPE, OPT_UINT16_TYPE,
+ OPT_BINARY_TYPE);
+// iaddr
+RECORD_DECL(iaaddrRecords, OPT_IPV6_ADDRESS_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT32_TYPE);
+// ia-na
+RECORD_DECL(ianaRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-pd
+RECORD_DECL(iapdRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE, OPT_UINT32_TYPE);
+// ia-prefix
+RECORD_DECL(iaPrefixRecords, OPT_UINT32_TYPE, OPT_UINT32_TYPE,
+ OPT_UINT8_TYPE, OPT_BINARY_TYPE);
+// lq-query
+RECORD_DECL(lqQueryRecords, OPT_UINT8_TYPE, OPT_IPV6_ADDRESS_TYPE);
+// lq-relay-data
+RECORD_DECL(lqRelayData, OPT_IPV6_ADDRESS_TYPE, OPT_BINARY_TYPE);
+// remote-id
+RECORD_DECL(remoteIdRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// status-code
+RECORD_DECL(statusCodeRecords, OPT_UINT16_TYPE, OPT_STRING_TYPE);
+// vendor-class
+RECORD_DECL(vendorClassRecords, OPT_UINT32_TYPE, OPT_BINARY_TYPE);
+// vendor-opts
+RECORD_DECL(vendorOptsRecords, 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.
+static 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(ianaRecords) },
+ { "ia-ta", D6O_IA_TA, OPT_UINT32_TYPE, false, NO_RECORD_DEF },
+ { "iaaddr", D6O_IAADDR, OPT_RECORD_TYPE, false, RECORD_DEF(iaaddrRecords) },
+ { "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(statusCodeRecords) },
+ { "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(vendorClassRecords) },
+ { "vendor-opts", D6O_VENDOR_OPTS, OPT_RECORD_TYPE, false,
+ RECORD_DEF(vendorOptsRecords) },
+ { "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(iapdRecords) },
+ { "iaprefix", D6O_IAPREFIX, OPT_RECORD_TYPE, false,
+ RECORD_DEF(iaPrefixRecords) },
+ { "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(geoconfCivicRecords) },
+ { "remote-id", D6O_REMOTE_ID, OPT_RECORD_TYPE, false,
+ RECORD_DEF(remoteIdRecords) },
+ { "subscriber-id", D6O_SUBSCRIBER_ID, OPT_BINARY_TYPE, false,
+ NO_RECORD_DEF },
+ { "client-fqdn", D6O_CLIENT_FQDN, OPT_RECORD_TYPE, false,
+ RECORD_DEF(clientFqdnRecords) },
+ { "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(lqQueryRecords) },
+ { "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(lqRelayData) },
+ { "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 e66d700..5799d58 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -35,6 +35,7 @@ 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_data_types_unittest.cc
libdhcp___unittests_SOURCES += option_definition_unittest.cc
libdhcp___unittests_SOURCES += option_custom_unittest.cc
libdhcp___unittests_SOURCES += option_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/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index af3d6ca..8ead521 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -22,6 +22,7 @@
#include <dhcp/option6_iaaddr.h>
#include <dhcp/option6_int.h>
#include <dhcp/option6_int_array.h>
+#include <dhcp/option_custom.h>
#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -68,8 +69,8 @@ public:
const std::type_info& expected_type) {
// Get all option definitions, we will use them to extract
// the definition for a particular option code.
- // We don't have to initialize option deinitions here because they
- // are initialized in the class'es constructor.
+ // We don't have to initialize option definitions here because they
+ // are initialized in the class's constructor.
OptionDefContainer options = LibDHCP::getOptionDefs(Option::V6);
// Get the container index #1. This one allows for searching
// option definitions using option code.
@@ -90,13 +91,15 @@ public:
ASSERT_NO_THROW(def->validate());
OptionPtr option;
// Create the option.
- ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf));
+ ASSERT_NO_THROW(option = def->optionFactory(Option::V6, code, buf))
+ << "Option creation failed to 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;
}
};
@@ -399,24 +402,149 @@ TEST_F(LibDhcpTest, unpackOptions4) {
// This test have to be extended once all option definitions are
// created.
TEST_F(LibDhcpTest, stdOptionDefs6) {
- LibDhcpTest::testStdOptionDefs6(D6O_CLIENTID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, OptionBuffer(14, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, OptionBuffer(12, 1),
- typeid(Option6IA));
- LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, OptionBuffer(24, 1),
- typeid(Option6IAAddr));
- LibDhcpTest::testStdOptionDefs6(D6O_ORO, OptionBuffer(10, 1),
- typeid(Option6IntArray<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, OptionBuffer(2, 1),
- typeid(Option6Int<uint16_t>));
- LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, OptionBuffer(10, 1),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, OptionBuffer(),
- typeid(Option));
- LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, OptionBuffer(32, 1),
- typeid(Option6AddrLst));
+
+ // 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);
+
+ // 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, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SERVERID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_NA, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_TA, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAADDR, buf, typeid(Option6IAAddr));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ORO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PREFERENCE, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ELAPSED_TIME, buf,
+ typeid(Option6Int<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RELAY_MSG, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_STATUS_CODE, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RAPID_COMMIT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_USER_CLASS, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_CLASS, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_VENDOR_OPTS, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INTERFACE_ID, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_MSG, buf,
+ typeid(Option6Int<uint8_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_RECONF_ACCEPT, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_DNS, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SIP_SERVERS_ADDR, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NAME_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_DOMAIN_SEARCH, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IA_PD, buf, typeid(Option6IA));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_IAPREFIX, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NIS_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NISP_DOMAIN_NAME, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SNTP_SERVERS, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_INFORMATION_REFRESH_TIME,
+ buf, typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_D, fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_BCMCS_SERVER_A, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_GEOCONF_CIVIC, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_REMOTE_ID, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_SUBSCRIBER_ID, buf,typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_FQDN, client_fqdn_buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_PANA_AGENT, buf,
+ typeid(Option6AddrLst));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_POSIX_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_NEW_TZDB_TIMEZONE, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_ERO, buf,
+ typeid(Option6IntArray<uint16_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_QUERY, buf, typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLIENT_DATA, buf, typeid(Option));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_CLT_TIME, buf,
+ typeid(Option6Int<uint32_t>));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_RELAY_DATA, buf,
+ typeid(OptionCustom));
+
+ LibDhcpTest::testStdOptionDefs6(D6O_LQ_CLIENT_LINK, buf,
+ typeid(Option6AddrLst));
}
}
diff --git a/src/lib/dhcp/tests/option6_ia_unittest.cc b/src/lib/dhcp/tests/option6_ia_unittest.cc
index 1ee618a..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;
);
diff --git a/src/lib/dhcp/tests/option_custom_unittest.cc b/src/lib/dhcp/tests/option_custom_unittest.cc
index 34969ee..044279b 100644
--- a/src/lib/dhcp/tests/option_custom_unittest.cc
+++ b/src/lib/dhcp/tests/option_custom_unittest.cc
@@ -106,6 +106,17 @@ TEST_F(OptionCustomTest, constructor) {
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
@@ -113,9 +124,10 @@ TEST_F(OptionCustomTest, constructor) {
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, OptionBuffer()));
+ option.reset(new OptionCustom(opt_def, Option::V4, buf.begin(), buf.end()));
);
ASSERT_TRUE(option);
@@ -159,8 +171,10 @@ TEST_F(OptionCustomTest, binaryData) {
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, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V4, buf_in.begin(),
+ buf_in.end())),
isc::OutOfRange
);
}
@@ -197,12 +211,46 @@ TEST_F(OptionCustomTest, booleanData) {
EXPECT_FALSE(value);
// Check that the option with "no data" is rejected.
+ buf.clear();
EXPECT_THROW(
- option.reset(new OptionCustom(opt_def, Option::V6, OptionBuffer())),
+ 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) {
@@ -338,6 +386,7 @@ TEST_F(OptionCustomTest, ipv6AddressData) {
);
}
+
// 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) {
@@ -365,8 +414,9 @@ TEST_F(OptionCustomTest, stringData) {
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, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -419,8 +469,9 @@ TEST_F(OptionCustomTest, booleanDataArray) {
// 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, OptionBuffer())),
+ option.reset(new OptionCustom(opt_def, Option::V6, buf.begin(), buf.end())),
isc::OutOfRange
);
}
@@ -472,7 +523,6 @@ TEST_F(OptionCustomTest, uint32DataArray) {
buf.begin() + 3)),
isc::OutOfRange
);
-
}
// The purpose of this test is to verify that the option definition comprising
@@ -575,6 +625,45 @@ TEST_F(OptionCustomTest, ipv6AddressDataArray) {
);
}
+// 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.
@@ -584,20 +673,30 @@ TEST_F(OptionCustomTest, recordData) {
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.
+ // 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 IPv4 address.
+ // 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 3 to IPv6 address.
+ // Initialize field 4 to IPv6 address.
writeAddress(IOAddress("2001:db8:1::1"), buf);
- // Initialize field 4 to string value.
+ // Initialize field 5 to string value.
writeString("ABCD", buf);
boost::scoped_ptr<OptionCustom> option;
@@ -606,8 +705,8 @@ TEST_F(OptionCustomTest, recordData) {
);
ASSERT_TRUE(option);
- // We should have 5 data fields.
- ASSERT_EQ(5, option->getDataFieldsNum());
+ // We should have 6 data fields.
+ ASSERT_EQ(6, option->getDataFieldsNum());
// Verify value in the field 0.
uint16_t value0 = 0;
@@ -620,19 +719,24 @@ TEST_F(OptionCustomTest, recordData) {
EXPECT_TRUE(value1);
// Verify value in the field 2.
- IOAddress value2("127.0.0.1");
- ASSERT_NO_THROW(value2 = option->readAddress(2));
- EXPECT_EQ("192.168.0.1", value2.toText());
+ std::string value2 = "";
+ ASSERT_NO_THROW(value2 = option->readFqdn(2));
+ EXPECT_EQ("mydomain.example.com.", value2);
// Verify value in the field 3.
- IOAddress value3("::1");
+ IOAddress value3("127.0.0.1");
ASSERT_NO_THROW(value3 = option->readAddress(3));
- EXPECT_EQ("2001:db8:1::1", value3.toText());
+ EXPECT_EQ("192.168.0.1", value3.toText());
// Verify value in the field 4.
- std::string value4;
- ASSERT_NO_THROW(value4 = option->readString(4));
- EXPECT_EQ("ABCD", value4);
+ 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
@@ -683,6 +787,413 @@ TEST_F(OptionCustomTest, recordDataTruncated) {
);
}
+// 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) {
@@ -901,6 +1412,4 @@ TEST_F(OptionCustomTest, invalidIndex) {
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..748a84b
--- /dev/null
+++ b/src/lib/dhcp/tests/option_data_types_unittest.cc
@@ -0,0 +1,491 @@
+// 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) {
+ short family = address.getFamily();
+ if (family == AF_INET) {
+ asio::ip::address_v4::bytes_type buf_addr =
+ address.getAddress().to_v4().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.end());
+ } else if (family == AF_INET6) {
+ asio::ip::address_v6::bytes_type buf_addr =
+ address.getAddress().to_v6().to_bytes();
+ buf.insert(buf.end(), buf_addr.begin(), buf_addr.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 20c87d6..91b822c 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -23,6 +23,7 @@
#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 <exceptions/exceptions.h>
@@ -883,7 +884,7 @@ TEST_F(OptionDefinitionTest, utf8StringTokenized) {
option_v6 = opt_def.optionFactory(Option::V6, opt_code, values);
);
ASSERT_TRUE(option_v6);
- ASSERT_TRUE(typeid(*option_v6) == typeid(Option));
+ 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());
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..2ef982a 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
@@ -416,6 +416,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,6 +456,10 @@ 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);
}
static uint8_t v4Opts[] = {
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 6c08acd..37fd953 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -8,6 +8,18 @@ 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 nsas_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
@@ -20,6 +32,7 @@ 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 += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
@@ -31,6 +44,8 @@ libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.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
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index a1d856c..784aeab 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.
///
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 3a53887..77a2df4 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -67,7 +67,7 @@ AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
const Pool6Collection& pools = subnet->getPools();
- if (pools.size() == 0) {
+ if (pools.empty()) {
isc_throw(AllocFailed, "No pools defined in selected subnet");
}
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 3c46b13..7dc5f55 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -101,6 +101,14 @@ void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
subnets4_.push_back(subnet);
}
+void CfgMgr::deleteSubnets4() {
+ subnets4_.clear();
+}
+
+void CfgMgr::deleteSubnets6() {
+ subnets6_.clear();
+}
+
CfgMgr::CfgMgr() {
}
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index 5bff64a..ac1b3f5 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -43,6 +43,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 +53,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
@@ -101,9 +104,9 @@ public:
/// 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 +114,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
///
@@ -130,7 +131,16 @@ public:
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.
diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox
index f954bed..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.
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..10ac3ee
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// 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.
+
+/// 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..6a805dc
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,60 @@
+// 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 (and verbose) data on the server.
+const int DHCPSRV_DBG_RTT = 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..dcc45ab
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,102 @@
+# 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_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_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
+are logged.
+
+% 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_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 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 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 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 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 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 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 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 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 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 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 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 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 f7b6373..74e49e8 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -55,7 +55,19 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
}
std::string
-Lease6::toText() {
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_.toText() << "\n"
+ << "Valid life: " << valid_lft_ << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n";
+
+ return (stream.str());
+}
+
+std::string
+Lease6::toText() const {
ostringstream stream;
stream << "Type: " << static_cast<int>(type_) << " (";
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index a264743..4f82fef 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -30,7 +30,7 @@
#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.
@@ -118,32 +118,6 @@ struct Lease4 {
/// @brief Maximum size of a hardware address
static const size_t HWADDR_MAX = 20;
- /// @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(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
- const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
- time_t cltt, uint32_t subnet_id)
- : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
- client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
- valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
- fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
- comments_()
- {}
-
- /// @brief Default Constructor
- ///
- /// Initialize fields that don't have a default constructor.
- Lease4() : addr_(0) {}
-
/// IPv4 address
isc::asiolink::IOAddress addr_;
@@ -165,7 +139,7 @@ struct Lease4 {
///
/// @todo Should this be a pointer to a client ID or the ID itself?
/// Compare with the DUID in the Lease6 structure.
- boost::shared_ptr<ClientId> client_id_;
+ ClientIdPtr client_id_;
/// @brief Renewal timer
///
@@ -227,6 +201,38 @@ struct Lease4 {
/// system administrator.
std::string comments_;
+ /// @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(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len,
+ const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
+ time_t cltt, uint32_t subnet_id)
+ : addr_(addr), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+ client_id_(new ClientId(clientid, clientid_len)), t1_(0), t2_(0),
+ valid_lft_(valid_lft), cltt_(cltt), subnet_id_(subnet_id),
+ fixed_(false), hostname_(), fqdn_fwd_(false), fqdn_rev_(false),
+ comments_()
+ {}
+
+ /// @brief Default constructor
+ ///
+ /// Initialize fields that don't have a default constructor.
+ Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
+ {}
+
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual represenation of lease data
+ std::string toText() const;
+
/// @brief Compare two leases for equality
///
/// @param other lease6 object with which to compare
@@ -265,11 +271,6 @@ struct Lease6 {
LEASE_IA_PD /// the lease contains IPv6 prefix (for prefix delegation)
} LeaseType;
- /// @brief Constructor
- 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 IPv6 address
///
/// IPv6 address or, in the case of a prefix delegation, the prefix.
@@ -367,14 +368,21 @@ struct Lease6 {
/// @todo: Add DHCPv6 failover related fields here
/// @brief Constructor
+ 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 Constructor
///
/// Initialize fields that don't have a default constructor.
- Lease6() : addr_("::") {}
+ Lease6() : addr_("::"), type_(LEASE_IA_NA), fixed_(false), fqdn_fwd_(false),
+ fqdn_rev_(false)
+ {}
/// @brief Convert Lease6 to Printable Form
///
/// @return String form of the lease
- std::string toText();
+ std::string toText() const;
/// @brief Compare two leases for equality
///
@@ -443,19 +451,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.
@@ -568,17 +563,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..06e2f79 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,68 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
return (mapped_tokens);
}
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+ // Reconstruct the access string
+ std::string access = "";
+ for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter
+ access += i->first;
+ access += "=";
+
+ // Redact the password
+ 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..da86d5f 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 dbaccess 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/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 8a59b73..b71b166 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -49,11 +49,6 @@ Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
return (Lease4Collection());
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&,
- SubnetID) const {
- return (Lease4Ptr());
-}
-
Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
SubnetID) const {
return (Lease4Ptr());
@@ -102,18 +97,21 @@ void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
}
-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
+bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ if (addr.isV4()) {
+ // V4 not implemented yet
return (false);
+
} 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);
+ }
}
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 3f54edc..268b722 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -68,16 +68,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
@@ -176,17 +166,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
///
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index 73289ad..deb4288 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,14 +16,15 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
#include <mysql/mysqld_error.h>
-#include <algorithm>
#include <iostream>
#include <iomanip>
+#include <sstream>
#include <string>
#include <time.h>
@@ -191,8 +192,38 @@ TaggedStatement tagged_statements[] = {
{MySqlLeaseMgr::NUM_STATEMENTS, NULL}
};
+/// @brief Produce string representation of hardware address
+///
+/// Returns a string containing the hardware address. This is only used for
+/// logging.
+///
+/// @note Six characters is an arbitrary length, chosen to provide a
+/// suitably wide string.
+///
+/// @todo Create a "hardware address" class of which this will be a member.
+///
+/// @param hwaddr Hardware address to convert to string form
+///
+/// @return String form of the hardware address.
+std::string
+hardwareAddressString(const LeaseMgr::HWAddr& hwaddr) {
+ std::ostringstream stream;
+
+ for (size_t i = 0; i < hwaddr.size(); ++i) {
+ if (i > 0) {
+ stream << ":";
+ }
+ stream << std::setw(2) << std::setfill('0')
+ << static_cast<unsigned int>(hwaddr[i]);
+ }
+
+ return (stream.str());
+}
+
}; // Anonymous namespace
+
+
namespace isc {
namespace dhcp {
@@ -1112,6 +1143,9 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
bool
MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_ADD_ADDR4)
+ .arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1121,6 +1155,9 @@ MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
bool
MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_ADD_ADDR6)
+ .arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the lease
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1257,6 +1294,9 @@ void MySqlLeaseMgr::getLease(StatementIndex stindex, MYSQL_BIND* bind,
Lease4Ptr
MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_ADDR4)
+ .arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1274,26 +1314,11 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
}
-Lease4Ptr
-MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr,
- SubnetID subnet_id) const {
-
- // As the address is the unique primary key of the lease4 table, there can
- // only be one lease with a given address. Therefore we will get that
- // lease and do the filtering on subnet ID here.
- Lease4Ptr result = getLease4(addr);
- if (result && (result->subnet_id_ != subnet_id)) {
-
- // Lease found but IDs do not match. Return null pointer
- result.reset();
- }
-
- return (result);
-}
-
-
Lease4Collection
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_HWADDR)
+ .arg(hardwareAddressString(hwaddr));
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1321,6 +1346,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hardwareAddressString(hwaddr));
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1352,6 +1380,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_CLIENTID)
+ .arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1373,6 +1404,9 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, 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));
@@ -1398,6 +1432,9 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_ADDR6)
+ .arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1421,6 +1458,8 @@ MySqlLeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Collection
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_IAID_DUID)
+ .arg(iaid).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -1462,6 +1501,8 @@ MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid) const {
Lease6Ptr
MySqlLeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
@@ -1529,6 +1570,9 @@ void
MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE4;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, 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);
@@ -1551,6 +1595,9 @@ void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, 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);
@@ -1573,12 +1620,13 @@ MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
updateLeaseCommon(stindex, &bind[0], lease);
}
-// Delete lease methods. As with other groups of methods, these comprise
-// a per-type method that sets up the relevant MYSQL_BIND array and a
-// common method than handles the common processing.
+// 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::deleteLease(StatementIndex stindex, MYSQL_BIND* bind) {
+MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
// Bind the input parameters to the statement
int status = mysql_stmt_bind_param(statements_[stindex], bind);
@@ -1595,42 +1643,36 @@ MySqlLeaseMgr::deleteLease(StatementIndex stindex, MYSQL_BIND* bind) {
bool
-MySqlLeaseMgr::deleteLease4(const isc::asiolink::IOAddress& addr) {
+MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DELETE_ADDR)
+ .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);
-
- // 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_LONG;
- inbind[0].buffer = reinterpret_cast<char*>(&addr4);
- inbind[0].is_unsigned = MLM_TRUE;
-
- return (deleteLease(DELETE_LEASE4, inbind));
-}
-
+ if (addr.isV4()) {
+ uint32_t addr4 = static_cast<uint32_t>(addr);
-bool
-MySqlLeaseMgr::deleteLease6(const isc::asiolink::IOAddress& addr) {
+ inbind[0].buffer_type = MYSQL_TYPE_LONG;
+ inbind[0].buffer = reinterpret_cast<char*>(&addr4);
+ inbind[0].is_unsigned = MLM_TRUE;
- // Set up the WHERE clause value
- MYSQL_BIND inbind[1];
- memset(inbind, 0, sizeof(inbind));
+ return (deleteLeaseCommon(DELETE_LEASE4, inbind));
- std::string addr6 = addr.toText();
- unsigned long addr6_length = addr6.size();
+ } else {
+ std::string addr6 = addr.toText();
+ unsigned long addr6_length = addr6.size();
- // 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;
+ // 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 (deleteLease(DELETE_LEASE6, inbind));
+ return (deleteLeaseCommon(DELETE_LEASE6, inbind));
+ }
}
// Miscellaneous database methods.
@@ -1657,6 +1699,8 @@ std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion() const {
const StatementIndex stindex = GET_VERSION;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_GET_VERSION);
+
uint32_t major; // Major version number
uint32_t minor; // Minor version number
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 8a97fc0..68b0e1c 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -94,19 +94,6 @@ 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.
@@ -286,33 +273,16 @@ public:
/// failed.
virtual void updateLease6(const Lease6Ptr& lease6);
- /// @brief Deletes an IPv4 lease.
- ///
- /// @todo Merge with deleteLease6: it is possible to determine whether
- /// an address is V4 or V6 from the IOAddress argument, so there
- /// is no need for separate V4 or V6 methods.
- ///
- /// @param addr IPv4 address of the lease to be deleted.
- ///
- /// @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 deleteLease4(const isc::asiolink::IOAddress& addr);
-
- /// @brief Deletes an IPv6 lease.
- ///
- /// @todo Merge with deleteLease4: it is possible to determine whether
- /// an address is V4 or V6 from the IOAddress argument, so there
- /// is no need for separate V4 or V6 methods.
+ /// @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 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
///
@@ -613,7 +583,7 @@ private:
///
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- bool deleteLease(StatementIndex stindex, MYSQL_BIND* bind);
+ bool deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind);
/// @brief Check Error and Throw Exception
///
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index aa1ef1f..c7d7ac7 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -172,7 +172,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
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index e343c44..e83541d 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
@@ -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,86 @@ 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(LeaseMgr, redactAccessString) {
+
+ // To check the redacted string, break it down into its component
+ // parameters and check the results.
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea type=mysql");
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+
+ // ... and break the redacted string down into its components.
+ 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"]);
+
+ // Do the same, but check it works for an empty password.
+ parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea type=mysql");
+ 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"]);
+
+ // Do the same, but check it works in the absence of a password token
+ parameters = LeaseMgrFactory::parse("user=me name=kea type=mysql");
+ 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 6384c1c..a812811 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -73,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
@@ -179,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);
}
diff --git a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
index b3f504f..08186dc 100644
--- a/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc
@@ -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 7f6bcc9..746ef00 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -230,8 +230,8 @@ public:
///
/// @param address Address to use for the initialization
///
- /// @return Lease4Ptr. This will not point to anything if the initialization
- /// failed (e.g. unknown address).
+ /// @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());
@@ -251,7 +251,7 @@ public:
// 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_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x42)));
lease->valid_lft_ = 8677;
lease->cltt_ = 168256;
@@ -259,7 +259,7 @@ public:
} else if (address == straddress4_[1]) {
lease->hwaddr_ = vector<uint8_t>(6, 0x19);
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x53)));
lease->valid_lft_ = 3677;
lease->cltt_ = 123456;
@@ -267,7 +267,7 @@ public:
} else if (address == straddress4_[2]) {
lease->hwaddr_ = vector<uint8_t>(6, 0x2a);
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x64)));
lease->valid_lft_ = 5412;
lease->cltt_ = 234567;
@@ -275,7 +275,7 @@ public:
} else if (address == straddress4_[3]) {
lease->hwaddr_ = vector<uint8_t>(6, 0x19); // Same as lease 1
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x75)));
// The times used in the next tests are deliberately restricted - we
@@ -289,7 +289,7 @@ public:
} else if (address == straddress4_[4]) {
lease->hwaddr_ = vector<uint8_t>(6, 0x4c);
// Same ClientId as straddr4_[1]
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
lease->valid_lft_ = 7736;
lease->cltt_ = 222456;
@@ -298,7 +298,7 @@ public:
} 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_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
lease->valid_lft_ = 7832;
lease->cltt_ = 227476;
@@ -307,7 +307,7 @@ public:
} else if (address == straddress4_[6]) {
lease->hwaddr_ = vector<uint8_t>(6, 0x6e);
// Same ClientId as straddress4_1
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>(8, 0x53))); // Same as lease 1
lease->valid_lft_ = 1832;
lease->cltt_ = 627476;
@@ -315,7 +315,7 @@ public:
} else if (address == straddress4_[7]) {
lease->hwaddr_ = vector<uint8_t>(); // Empty
- lease->client_id_ = boost::shared_ptr<ClientId>(
+ lease->client_id_ = ClientIdPtr(
new ClientId(vector<uint8_t>())); // Empty
lease->valid_lft_ = 7975;
lease->cltt_ = 213876;
@@ -469,7 +469,7 @@ public:
///
/// @param leases Vector of pointers to leases
template <typename T>
- void checkLeasesDifferent(const std::vector<T> leases) const {
+ void checkLeasesDifferent(const std::vector<T>& leases) const {
// Check they were created
for (int i = 0; i < leases.size(); ++i) {
@@ -708,7 +708,8 @@ TEST_F(MySqlLeaseMgrTest, checkVersion) {
/// @brief Basic Lease4 Checks
///
-/// Checks that the addLease, getLease4 (by address) and deleteLease4 works.
+/// 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();
@@ -740,10 +741,10 @@ TEST_F(MySqlLeaseMgrTest, basicLease4) {
// Delete a lease, check that it's gone, and that we can't delete it
// a second time.
- EXPECT_TRUE(lmptr_->deleteLease4(ioaddress4_[1]));
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress4_[1]));
l_returned = lmptr_->getLease4(ioaddress4_[1]);
EXPECT_FALSE(l_returned);
- EXPECT_FALSE(lmptr_->deleteLease4(ioaddress4_[1]));
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress4_[1]));
// Check that the second address is still there.
l_returned = lmptr_->getLease4(ioaddress4_[2]);
@@ -753,7 +754,8 @@ TEST_F(MySqlLeaseMgrTest, basicLease4) {
/// @brief Basic Lease6 Checks
///
-/// Checks that the addLease, getLease6 (by address) and deleteLease6 works.
+/// 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();
@@ -785,10 +787,10 @@ TEST_F(MySqlLeaseMgrTest, basicLease6) {
// Delete a lease, check that it's gone, and that we can't delete it
// a second time.
- EXPECT_TRUE(lmptr_->deleteLease6(ioaddress6_[1]));
+ EXPECT_TRUE(lmptr_->deleteLease(ioaddress6_[1]));
l_returned = lmptr_->getLease6(ioaddress6_[1]);
EXPECT_FALSE(l_returned);
- EXPECT_FALSE(lmptr_->deleteLease6(ioaddress6_[1]));
+ EXPECT_FALSE(lmptr_->deleteLease(ioaddress6_[1]));
// Check that the second address is still there.
l_returned = lmptr_->getLease6(ioaddress6_[2]);
@@ -796,45 +798,6 @@ TEST_F(MySqlLeaseMgrTest, basicLease6) {
detailCompareLease(leases[2], l_returned);
}
-/// @brief Check GetLease4 methods - access by Address and SubnetID
-///
-/// Adds leases to the database and checks that they can be accessed via
-/// a the hardware address
-TEST_F(MySqlLeaseMgrTest, getLease4AddressSubnetId) {
- // Get the leases to be used for the test.
- vector<Lease4Ptr> leases = createLeases4();
- const SubnetID lease1_subnetid = leases[1]->subnet_id_;
-
- // Generate a Subnet ID known to be invalid - one more than the maximum
- // Subnet ID in all the leases.
- SubnetID invalid_subnetid = 0;
- for (int i = 0; i < leases.size(); ++i) {
- invalid_subnetid = max(invalid_subnetid, leases[i]->subnet_id_);
- }
- ++invalid_subnetid;
-
-
- // Add just one to the database.
- EXPECT_TRUE(lmptr_->addLease(leases[1]));
-
- // Look for a known lease with a valid Subnet ID
- Lease4Ptr l_returned = lmptr_->getLease4(ioaddress4_[1], lease1_subnetid);
- ASSERT_TRUE(l_returned);
- detailCompareLease(leases[1], l_returned);
-
- // Look for a lease known to be in the database with an invalid Subnet ID
- l_returned = lmptr_->getLease4(ioaddress4_[1], invalid_subnetid);
- EXPECT_FALSE(l_returned);
-
- // Look for a lease known not to be in the database with a valid Subnet ID
- l_returned = lmptr_->getLease4(ioaddress4_[2], lease1_subnetid);
- EXPECT_FALSE(l_returned);
-
- // Look for a lease known not to be in the database with and invalid
- l_returned = lmptr_->getLease4(ioaddress4_[2], invalid_subnetid);
- EXPECT_FALSE(l_returned);
-}
-
/// @brief Check GetLease4 methods - access by Hardware Address
///
/// Adds leases to the database and checks that they can be accessed via
@@ -896,7 +859,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
- (void) lmptr_->deleteLease4(leases[1]->addr_);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
}
// Expect some problem when accessing a lease that had too long a hardware
@@ -954,7 +917,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// "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_->deleteLease4(leases[2]->addr_));
+ EXPECT_TRUE(lmptr_->deleteLease(leases[2]->addr_));
leases[1]->addr_ = leases[2]->addr_;
EXPECT_TRUE(lmptr_->addLease(leases[1]));
EXPECT_THROW(returned = lmptr_->getLease4(leases[1]->hwaddr_,
@@ -964,7 +927,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Delete all leases in the database
for (int i = 0; ADDRESS4[i] != NULL; ++i) {
IOAddress addr(ADDRESS4[i]);
- (void) lmptr_->deleteLease4(addr);
+ (void) lmptr_->deleteLease(addr);
}
}
@@ -986,7 +949,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
- (void) lmptr_->deleteLease4(leases[1]->addr_);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
}
// Expect some error when getting a lease with too long a hardware
@@ -1069,7 +1032,7 @@ TEST_F(MySqlLeaseMgrTest, getLease4ClientIdSize) {
Lease4Collection returned = lmptr_->getLease4(*leases[1]->client_id_);
ASSERT_TRUE(returned.size() == 1);
detailCompareLease(leases[1], *returned.begin());
- (void) lmptr_->deleteLease4(leases[1]->addr_);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
}
// Don't bother to check client IDs longer than the maximum -
@@ -1179,7 +1142,7 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSize) {
leases[1]->iaid_);
EXPECT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
- (void) lmptr_->deleteLease6(leases[1]->addr_);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
}
// Don't bother to check DUIDs longer than the maximum - these cannot be
@@ -1245,7 +1208,7 @@ TEST_F(MySqlLeaseMgrTest, getLease6DuidIaidSubnetIdSize) {
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
- (void) lmptr_->deleteLease6(leases[1]->addr_);
+ (void) lmptr_->deleteLease(leases[1]->addr_);
}
// Don't bother to check DUIDs longer than the maximum - these cannot be
@@ -1293,7 +1256,7 @@ TEST_F(MySqlLeaseMgrTest, updateLease4) {
detailCompareLease(leases[1], l_returned);
// Try updating a lease not in the database.
- lmptr_->deleteLease4(ioaddress4_[2]);
+ lmptr_->deleteLease(ioaddress4_[2]);
EXPECT_THROW(lmptr_->updateLease4(leases[2]), isc::dhcp::NoSuchLease);
}
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/dns/Makefile.am b/src/lib/dns/Makefile.am
index 15f3421..7778b58 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -102,6 +102,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
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index b3b78c0..e6e2c78 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -149,6 +149,11 @@ MasterLexer::popSource() {
impl_->has_previous_ = false;
}
+size_t
+MasterLexer::getSourceCount() const {
+ return (impl_->sources_.size());
+}
+
std::string
MasterLexer::getSourceName() const {
if (impl_->sources_.empty()) {
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 35586fe..cdf2866 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -87,8 +87,7 @@ 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
@@ -405,6 +404,11 @@ public:
/// \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
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
new file mode 100644
index 0000000..0a3db39
--- /dev/null
+++ b/src/lib/dns/master_loader.cc
@@ -0,0 +1,367 @@
+// 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/rrttl.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rdata.h>
+
+#include <string>
+#include <memory>
+#include <boost/algorithm/string/predicate.hpp> // for iequals
+
+using std::string;
+using std::auto_ptr;
+using boost::algorithm::iequals;
+
+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),
+ 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),
+ complete_(false),
+ seen_error_(false)
+ {}
+
+ 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());
+ }
+ }
+
+ void pushSource(const std::string& filename) {
+ 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;
+ }
+ }
+ initialized_ = true;
+ }
+
+ bool popSource() {
+ if (lexer_.getSourceCount() == 1) {
+ return (false);
+ }
+ lexer_.popSource();
+ return (true);
+ }
+
+ void pushStreamSource(std::istream& stream) {
+ lexer_.pushSource(stream);
+ initialized_ = 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_);
+ }
+
+ bool loadIncremental(size_t count_limit);
+
+ void doInclude() {
+ // First, get the filename to include
+ const string
+ filename(lexer_.getNextToken(MasterToken::QSTRING).getString());
+
+ // There could be an origin (or maybe not). So try looking
+ const MasterToken name_tok(lexer_.getNextToken(MasterToken::QSTRING,
+ true));
+
+ if (name_tok.getType() == MasterToken::QSTRING ||
+ name_tok.getType() == MasterToken::STRING) {
+ // TODO: Handle the origin. Once we complete #2427.
+ } else {
+ // We return the newline there. This is because after we pop
+ // the source, we want to call eatUntilEOL and this would
+ // eat to the next one.
+ lexer_.ungetToken();
+ }
+
+ pushSource(filename);
+ }
+
+ void handleDirective(const char* directive, size_t length) {
+ if (iequals(directive, "INCLUDE")) {
+ doInclude();
+ } else if (iequals(directive, "ORIGIN")) {
+ // TODO: Implement
+ isc_throw(isc::NotImplemented,
+ "Origin directive not implemented yet");
+ } else if (iequals(directive, "TTL")) {
+ // TODO: Implement
+ isc_throw(isc::NotImplemented,
+ "TTL directive not implemented yet");
+ } 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_;
+ const RRClass zone_class_;
+ MasterLoaderCallbacks callbacks_;
+ AddRRCallback add_callback_;
+ 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)
+public:
+ bool complete_; // All work done.
+ bool seen_error_; // Was there at least one error during the
+ // load?
+};
+
+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_);
+ }
+ size_t count = 0;
+ while (ok_ && count < count_limit) {
+ try {
+ // Skip all EOLNs (empty lines) and finish on EOF
+ bool empty = true;
+ do {
+ const MasterToken& empty_token(lexer_.getNextToken());
+ if (empty_token.getType() == MasterToken::END_OF_FILE) {
+ if (!popSource()) {
+ return (true);
+ } else {
+ // We try to read a token from the popped source
+ // So retry the loop, but first, make sure the source
+ // is at EOL
+ eatUntilEOL(true);
+ continue;
+ }
+ }
+ empty = empty_token.getType() == MasterToken::END_OF_LINE;
+ } while (empty);
+ // Return the last token, as it was not empty
+ lexer_.ungetToken();
+
+ const MasterToken::StringRegion&
+ name_string(lexer_.getNextToken(MasterToken::QSTRING).
+ 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.
+ continue;
+ }
+
+ const Name name(name_string.beg, name_string.len,
+ &zone_origin_);
+ // TODO: Some more flexibility. We don't allow omitting
+ // anything yet
+
+ // The parameters
+ const RRTTL ttl(getString());
+ const RRClass rrclass(getString());
+ const RRType rrtype(getString());
+
+ // TODO: Some more validation?
+ if (rrclass != zone_class_) {
+ // It doesn't really matter much what type of exception
+ // we throw, we catch it just below.
+ isc_throw(isc::BadValue, "Class mismatch: " << rrclass <<
+ "vs. " << zone_class_);
+ }
+ // TODO: Check if it is SOA, it should be at the origin.
+
+ const rdata::RdataPtr data(rdata::createRdata(rrtype, rrclass,
+ lexer_,
+ &zone_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 (data) {
+ add_callback_(name, rrclass, rrtype, ttl, data);
+
+ // Good, we loaded another one
+ ++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_);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index 5ecdd77..9d8a755 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -15,20 +15,139 @@
#ifndef MASTER_LOADER_H
#define MASTER_LOADER_H
+#include <dns/master_loader_callbacks.h>
+
+#include <boost/noncopyable.hpp>
+
namespace isc {
namespace dns {
-// Placeholder introduced by #2497. The real class should be updated in
-// #2377.
-class MasterLoader {
+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 {
- MANY_ERRORS, // lenient mode
- // also eventually some check policies like "check NS name"
+ 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;
+
+private:
+ class MasterLoaderImpl;
+ MasterLoaderImpl* impl_;
};
-}
-}
+} // end namespace dns
+} // end namespace isc
#endif // MASTER_LOADER_H
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index 77d9ac5..f572194 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -23,20 +23,30 @@
namespace isc {
namespace dns {
+class Name;
+class RRClass;
+class RRType;
+class RRTTL;
+namespace rdata {
+class Rdata;
+typedef boost::shared_ptr<Rdata> RdataPtr;
+}
-class AbstractRRset;
-typedef boost::shared_ptr<AbstractRRset> RRsetPtr;
-
-/// \brief Type of callback to add a RRset.
+/// \brief Type of callback to add a RR.
///
/// This type of callback is used by the loader to report another loaded
-/// RRset. The RRset is no longer preserved by the loader and is fully
+/// RR. The Rdata is no longer preserved by the loader and is fully
/// owned by the callback.
///
-/// \param RRset The rrset to add. It does not contain the accompanying
-/// RRSIG (if the zone is signed), they are reported with separate
-/// calls to the callback.
-typedef boost::function<void(const RRsetPtr& rrset)> AddRRsetCallback;
+/// \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.
///
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/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/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/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index d5690d4..5f2ae5c 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -27,6 +27,7 @@ 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
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index b2415da..42ed3fb 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -60,8 +60,10 @@ TEST_F(MasterLexerTest, preOpen) {
}
TEST_F(MasterLexerTest, pushStream) {
+ EXPECT_EQ(0, lexer.getSourceCount());
lexer.pushSource(ss);
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ EXPECT_EQ(1, lexer.getSourceCount());
// From the point of view of this test, we only have to check (though
// indirectly) getSourceLine calls InputSource::getCurrentLine. It should
@@ -70,18 +72,22 @@ TEST_F(MasterLexerTest, pushStream) {
// By popping it the stack will be empty again.
lexer.popSource();
+ EXPECT_EQ(0, lexer.getSourceCount());
checkEmptySource(lexer);
}
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());
lexer.popSource();
checkEmptySource(lexer);
+ EXPECT_EQ(0, lexer.getSourceCount());
// If we give a non NULL string pointer, its content will be intact
// if pushSource succeeds.
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..bfbf9fa
--- /dev/null
+++ b/src/lib/dns/tests/master_loader_unittest.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 <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrclass.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+#include <gtest/gtest.h>
+#include <boost/bind.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;
+
+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))
+ {}
+
+ /// 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) {
+ 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());
+ 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");
+ }
+
+ 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());
+ loader_->load();
+ EXPECT_TRUE(loader_->loadedSucessfully());
+
+ EXPECT_TRUE(errors_.empty());
+ EXPECT_TRUE(warnings_.empty());
+
+ 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 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 don't test without 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) {
+ stringstream zone_stream(prepareZone("", true));
+ setLoader(zone_stream, Name("example.org."), RRClass::IN(),
+ MasterLoader::MANY_ERRORS);
+
+ EXPECT_FALSE(loader_->loadedSucessfully());
+ 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");
+}
+
+// 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");
+}
+
+// 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 problem; // Description of the problem for SCOPED_TRACE
+} const error_cases[] = {
+ { "www... 3600 IN A 192.0.2.1", "Invalid name" },
+ { "www FORTNIGHT IN A 192.0.2.1", "Invalid TTL" },
+ { "www 3600 XX A 192.0.2.1", "Invalid class" },
+ { "www 3600 IN A bad_ip", "Invalid Rdata" },
+ { "www 3600 IN", "Unexpected EOLN" },
+ { "www 3600 CH TXT nothing", "Class mismatch" },
+ { "www \"3600\" IN A 192.0.2.1", "Quoted TTL" },
+ { "www 3600 \"IN\" A 192.0.2.1", "Quoted class" },
+ { "www 3600 IN \"A\" 192.0.2.1", "Quoted type" },
+ { "unbalanced)paren 3600 IN A 192.0.2.1", "Token error 1" },
+ { "www 3600 unbalanced)paren A 192.0.2.1", "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", "Unknown $ directive" },
+ { "$INCLUD " TEST_DATA_SRCDIR "/example.org", "Include too short" },
+ { "$INCLUDES " TEST_DATA_SRCDIR "/example.org", "Include too long" },
+ { "$INCLUDE", "Missing include path" },
+ { "$INCLUDE /file/not/found", "Include file not found" },
+ { "$INCLUDE /file/not/found and here goes bunch of garbage",
+ "Include file not found and garbage at the end of line" },
+ { 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());
+ 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());
+ // 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");
+}
+
+// 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);
+}
+
+// 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()) << errors_[0];
+ // 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");
+}
+
+}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 94cadec..9494117 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -170,6 +170,8 @@ 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
.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..2f2edf3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/example.org
@@ -0,0 +1,15 @@
+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.
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/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/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/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 6357e1f..6862d0c 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -23,12 +23,14 @@
#include <log4cplus/syslogappender.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 +42,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 +72,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 +140,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 +163,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,22 +177,22 @@ 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();
@@ -191,14 +207,14 @@ 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)
{
@@ -225,5 +241,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/Makefile.am b/src/lib/log/tests/Makefile.am
index 7f96077..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,7 @@ 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
@@ -131,6 +143,7 @@ check-local:
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/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index aa0547b..a200dfc 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,8 +224,13 @@ 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"""
@@ -313,11 +318,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/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index e0c0f06..83bf0ed 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -88,6 +88,35 @@ 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\
+Apart from the two exceptions mentioned below, in theory this call can\n\
+throw anything, depending on the implementation of the datasource\n\
+backend.\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.\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_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..d0206d1 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -92,6 +92,37 @@ 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_getIterator(PyObject* po_self, PyObject* args) {
s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
PyObject* name_obj;
@@ -230,6 +261,8 @@ 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 },
{ "get_iterator",
DataSourceClient_getIterator, METH_VARARGS,
DataSourceClient_getIterator_doc },
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index b8e6fb2..fef04a4 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,53 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(None, iterator.get_soa())
self.assertEqual(None, iterator.get_next_rrset())
+ def test_create_zone_args(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+
+ self.assertRaises(TypeError, dsc.create_zone)
+ self.assertRaises(TypeError, dsc.create_zone, 1)
+ self.assertRaises(TypeError, dsc.create_zone, None)
+ self.assertRaises(TypeError, dsc.create_zone, "foo.")
+ self.assertRaises(TypeError, dsc.create_zone,
+ isc.dns.Name("example.org"), 1)
+
+ def test_create_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)
+
+ 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))
+
+ 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)
+
+ # Cancel updater, then create should succeed
+ updater = None
+ self.assertTrue(dsc.create_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/log/log.cc b/src/lib/python/isc/log/log.cc
index 69e70b7..7b95201 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -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/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/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
"""
More information about the bind10-changes
mailing list