BIND 10 trac2571_commit_log_fix, updated. 525333e187cc4bbbbde288105c9582c1024caa4a Merge branch 'master' into trac2571_commit_log_fix
BIND 10 source code commits
bind10-changes at lists.isc.org
Mon Jan 21 19:34:22 UTC 2013
The branch, trac2571_commit_log_fix has been updated
via 525333e187cc4bbbbde288105c9582c1024caa4a (commit)
via 2023dc6ee89d9e43dccbf85e55505ef54e4e8895 (commit)
via ca962cbac01e4c3c79050b57ca07165c3b7662ae (commit)
via 016925ef2437e0396127e135c937d3a55539d224 (commit)
via c4df99eac2910af079f19b0bdacc95d7cd88192f (commit)
via 99a6e8470907e678cd8c208eeef2810b4bd0bacb (commit)
via 6167f31e01864656cf1a26a0d6bea75f08783a01 (commit)
via 32973fceb52e1da15903df796fc1fa55dc3486e4 (commit)
via 0a89b374d57877e3b1ea1f379276d74a34ea7d8f (commit)
via 95d6795825d9df9634d2282b28d1d9a5761b6634 (commit)
via 39a3150a0fe822b177c53379b2c93ad6f24585b9 (commit)
via 610f3ecc55290ff4c2d3453f0f90c222b00a6f38 (commit)
via 5204b8af562627419798146f71f7402849bfaedc (commit)
via a70f6172194a976b514cd7d67ce097bbca3c2798 (commit)
via 0a433790559aa0e0fe148a06bbe50d9df522312a (commit)
via f2c0ce1adf8090ecb9ed76e6f3f201d0b044e060 (commit)
via 79b57c7becdf6fa66812c75bf29a63f63221442f (commit)
via ced31d8c5a0f2ca930b976d3caecfc24fc04634e (commit)
via dd637a386748c2a393acc3d437c071a10b9cc7f8 (commit)
via 3aee00b231be08a7edecfedf833ab0d9f2629922 (commit)
via d3c90ab38ecda07729741cb67505c4e9a659c6be (commit)
via 980d715bc1b4b6dc592ac6c8e7bdedf4cf528f6c (commit)
via 0f244147145019810ae16b36383352cbf4672919 (commit)
via 9423db0d25e30b0a3e1334b56c0ae754675bb2ce (commit)
via fe931e2f1c65e3bde969aa3bb0a2a031201fab95 (commit)
via 003f49d97927cc4dd85c4a631611152e7e10bd1c (commit)
via fd29a774079b86f2b62c857d13ef450f0da29a00 (commit)
via e149d57f779c6597a6ed1d497c40a9641ac6651c (commit)
via ce19e77d9f6b9715502b5636511421dc09148f86 (commit)
via 88ff44432d5fed287aa8a91d620692f4a715ae1c (commit)
via f3709cc11e26e2bb2a0cfa4f447efce885160276 (commit)
via 39512e1b3142a1006cb793817691f4ce9fd7480c (commit)
via bdc99366fe025e5175e4a6e2c189e3d5ae1125f7 (commit)
via b88f6fb05d83b5ee7b7594630e02093398f995c7 (commit)
via fa342a994de5dbefe32996be7eebe58f6304cff7 (commit)
via 24e0b47e82d29460f4dabfb66558204db9b3b83d (commit)
via 738540ce3a7b5d06707d5b298a6223629ab66050 (commit)
via 3b37e9c152b05606bb5401c16c5b9f6d37155539 (commit)
via 81ed5b0185f7b37aafada49c52880ba87b03785f (commit)
via 37a8e164d7e3267b42ad438f49ae2505bd5b952b (commit)
via e3bde86b2e51188e37fab58965709fe2f22f9610 (commit)
via 2992f269ed62c67a5fb60ad60c0f7dcd89fba957 (commit)
via 2f061fe969a7af07cecd9cd5d166f5b2d5b327aa (commit)
via 6fbf5830da2a9ed728a865715bab0d59e1a5431b (commit)
via eda46b3f2aa8742c7ee7798383d59b401a07bc0f (commit)
via c84dafee22cf9c39e1dde4d15c62246ec25fc09a (commit)
via 48d999f1cb59f308f9f30ba2639521d2a5a85baa (commit)
via 61a7a86532aac5a0b84cadee0330f0eb20762ba6 (commit)
via f9169a71cb277c3733a8ff7d0fea87762196762d (commit)
via d47e9e008a7eb572f370ab7b5527c8188bb24017 (commit)
via 32a5e02895ad3faf098172a08c11ce4df535521e (commit)
via 7d4b55818c8510a180889b157bfb360ef601af1b (commit)
via 71e25eb81e58a695cf3bad465c4254b13a50696e (commit)
via 65eb1db45415266f4c3c435b799f066eaf96998b (commit)
via b0a6e5fff9ad673e6ba6a55797ab08b1349ecd8f (commit)
via 2283402c2b94545fa66cbc5e8f7294cb39924568 (commit)
via 2f3611a364d4ec7b2626921c22029b8dbc024404 (commit)
via 6a1247051d30f4eb5be35b661f74a90a51390cbf (commit)
via a7872f60cba423597e3049aa8acd466fbe29882d (commit)
via 973bfad40ba4fe946d53d200c7e27ab32d79d0de (commit)
via 275a72e95dc82e105e48654e1a3ed71a48b7840a (commit)
via 13674dda1cf3f4f16964c7d5fc72559e3d90b8c2 (commit)
via 5a326976867ae6a6f4c98d056b40d36ff1dbac22 (commit)
via 914eb9b64bfcf703c2538cbf20b9c82bfbdfb525 (commit)
via 8d31e215ffc21430b29276ed65dd44a57598f9b8 (commit)
via 1cee583601a4e084bf54cf90e0d52b79f0783700 (commit)
via d09c436d4bb61e4ac7e3a6c2f3836ac30b29c40d (commit)
via 2a337c756ad1064af1cbcaa8cd2c79be65f0b4d5 (commit)
via 28a20a3ee754d03ea90f13f76fa8e6d44c696b69 (commit)
via 2ad5c3c52b9106f68a6fb24a2ebfc85067877a04 (commit)
via 09c47d4fef3388cd1aa2aa7db402da67c4968d3f (commit)
via 48892724360a9c706de8c2ffaa5099dc1da211ea (commit)
via 2b05b3926ac85363b1063a12c9ac8d0ac9cda3de (commit)
via 0c28935d20983b280976f0a62e4b32de735b29a8 (commit)
via 568655c55d104fcee40289810dd4431188a989f1 (commit)
via f2f653c6f29d56c00fdb09af6ba54bd1ab36f31f (commit)
via 8bd08bf2531ad91b4d3f4ef4b8f304c2dfc9c249 (commit)
via caf2b100bf61d75cd98935bb59b6b39a718289a9 (commit)
via 75d921323cf83bf67ec24d28927ee472b517fa88 (commit)
via 715a6f61177570c411f2e63f82ae1b2269a654cf (commit)
via f45c804082f5ebfbe3339224083531fe86f2bfb2 (commit)
via 5eeb4b30cbea6d0f5588cf0ad542cdccd49510c1 (commit)
via 84ba4a728f827325e262bb91c2517f45efef5d7e (commit)
via 3b38bc023ff63f7567ebce3d9a1cea9fc8654d1d (commit)
via 27369e24c8d4acca1ab5905245b218e31747aee8 (commit)
via 804f48610620ea057b91857ed6d3c993f796c444 (commit)
via 329ab8b7d40b2bd34997da2635cf68e51795cbf3 (commit)
via 37322e5124dce0d69305553977cbcf30e659688a (commit)
via 4b9b3bdcb7d97d916248215846002b27d91c43c2 (commit)
via 84a994f8ef9bef873fd36a0837476f7b0a2d319c (commit)
via 695d9fc3bc135de080369b0b7213aca3c264216c (commit)
via 7800a308cb8db2193f8406e1ef4db1f7f6dd0332 (commit)
via 5f6ae1f6c5c0f484c1e2f3073d713784cf3c5373 (commit)
via 4c2bed018107dea2091ac26f51e81d2f14f33582 (commit)
via cc7b41e7ce61e745602748346c4b00b8b864bdef (commit)
via ac0eaa438f7b76f1f58d54070313e9ffc5417c26 (commit)
via 9a0d024c97ba896c577d59db04a0337d3c70221a (commit)
via ccf170eee20359d3661fc70a7ce02b965cc90a69 (commit)
via fd3de5ebc7bbc021ba4b36769f4541a1fad5fffc (commit)
via 76c52a9395ff0b7bcd9b911308d43da8171a7cd5 (commit)
via 7d705b35fe149cc0c6b93211f2733537b05bd905 (commit)
via 016a19585f32b9bdf57fc5aecdc3b073f3d1a482 (commit)
via 7cbed6378d88f921cf33b12d18d78e56400b4702 (commit)
via a3e0f04e9f735501a2ef08b7d3c0de3f22849fcc (commit)
via ae4b62b7f869cc7e66ef691ab20faff93566ac5b (commit)
via d7d8d1bf1a37b33891f09a2b3641c9bf5231d19d (commit)
via 38439b178eeddb7148df3da5ebab555b294cc967 (commit)
via 579f5abd033f56e772c6ee0286c58634d12aabfe (commit)
via 867d9a39ee23b4e02ef9d36d241688f0ad525e5d (commit)
via 1b32af4e5cb9349d34b468cd0333bbcfdb338d5a (commit)
via 58df436a73f5b3cbe6177aea37383c3ec00ca76a (commit)
via ca0fc023a12228a9ebc20e90921f8ab23ec3274b (commit)
via 30e71e8bfdb6049265dca14a8ce62b989fe67118 (commit)
via a032d84ef41ca563063fa15ad34250f87c05eb65 (commit)
via c3edda086f948bc9c6bd46c8b9198a2e5261eac2 (commit)
via 761ece75fb3184fc28ddedcb742cf0c94fb5ef5e (commit)
via bd51648e6e9c8eac8943db455e48b26c8b196b14 (commit)
via e906f116b8125b58fefb62a14281bbf36b2d7941 (commit)
via be258adbecd48f1d46e47566f54843db1b579b9c (commit)
via 02a31879b8d8db032883d6e28d57c2369f2299cf (commit)
via df03141495a568971ff26d78a229beaa5b373e19 (commit)
via 8e5e30b800b786cbcf8ff12db820bb1a7c75d69f (commit)
via 9f079c662135d45fac7be5b0b9282e7542c55e81 (commit)
via 2f5ba8955ec66c1c7af60b85272f03383b256fc0 (commit)
via 61e7fa26cfe5ec6cd4a381bdc9e5bc25437f2620 (commit)
via 2f7f7b1e0fb86c93c17b04489ab7e836a59fe622 (commit)
via b6d00d5fb3657b555c9f25d4c23721d277a7d7a4 (commit)
via 68709a07c59441cd101e750d72049a6218f0262b (commit)
via 019ca218027a218921519f205139b96025df2bb5 (commit)
via f09298e6e1512b32462f41a4ced3dcc1950e18aa (commit)
via 54cbb54f358dd83e6c87f23ed17c5df754a28dc7 (commit)
via 4baf3a4dcacaa5d2fe57dba1a8cb1eee68cd0f21 (commit)
via 6e2a0ba86f0607f3e9822a178a223e8a922de47a (commit)
via ec9d85afe154206e95b683eaa8733e0f453f4090 (commit)
via 0b3abc02d6b943026d5e60f6376d5c20d913bcb7 (commit)
via c9d46273b40b9be9902deaa081be0f1216d287c6 (commit)
via 6278daf2fc808fd4a311a97529815db1ef216e79 (commit)
via f8fb3f6f2f6f42888be0cd4e8c9df9502c944081 (commit)
via 29e27da67a1558281a52a5a1c233db3bf84efcb3 (commit)
via d86e308e8d1cf3c569f9ad1edf17bb4289da954c (commit)
via 9bc254508724e55db2ce9624a0a9322f4d3d9f31 (commit)
via 00980e1e05152e7b44968438dc846ea41f43c113 (commit)
via 355fb4b538030ec31101edd4b8ea6e2ae2d90e65 (commit)
via 60606cabb1c9584700b1f642bf2af21a35c64573 (commit)
via 34724a6251f8d624c2c69d06fc1ddb9c21f201f0 (commit)
via beef799a018be9b0be103973c9fa477a7d0ca6fb (commit)
via 5200f31617133fc3700b802a7299efbc3a980ec5 (commit)
via b82c412af1ef36000368f817f25aef23cc36728a (commit)
via 360a5bb6cb72862edbbe9a4cb0074360f1385e21 (commit)
via 6478f4a1f3d3fb66a7fc25887135bdf3403538bd (commit)
via 924221ee423612e1389ff98a88ddc127e283aa02 (commit)
via 7a8402a585b391394949582c14ffb9182c757979 (commit)
via 82a1d5c34e951687d67a6313fb42d9e75768adbc (commit)
via ead15091870f4dbd5a87640be3493e36d9e9da73 (commit)
via ecfc245797cd10313557991c778e686fa1f19b37 (commit)
via 1c54eac8eb70d3fc7c16b83a1269315e710021c1 (commit)
via 79cfa55b6202c07cbba0b0db6532ca2dff9e2a9c (commit)
via a9a2f80671f01894e3c2909e8a185c8fb7396977 (commit)
via bed3c88c25ea8f7e951317775e99ebce3340ca22 (commit)
via 2d95931bbb2efb3ae30b6fe7219958e40b51c4f8 (commit)
via adf283b9aa964639693d23a4e809fe67657583fb (commit)
via 4d277fcbe264e299596a2ddc15243c87565f6127 (commit)
via 84bc0b71b8c31013d702c206a3fb1b4d1b93e514 (commit)
via e567eee7d939fa75ac7ccee625178e179e47ad98 (commit)
via 3072c500b4c563cb22b7cdae805cdb6c1e99a408 (commit)
via 76ca31298b2e7f5d85904045aaa6560bcf7ff8d6 (commit)
via f603440ab46a86436b2208697f36bd9726fc0e03 (commit)
via 11b14c65cb4c01d5d5278f14ac5b292842f91e13 (commit)
via e81f84bdabd3a5b7370bd2f4c41d9c7de333fafd (commit)
via 989e0535b0a1b2746aab98aa00aef995eeb4696b (commit)
via b32cd0ea8778cac7a9f9e991d3c978dc7faf93cd (commit)
via 1ba7dea264623db66cbf62fed95db34cde362cf5 (commit)
via b927f6e09523bcadf613b4e409a0039e5bfbb03c (commit)
via 0cce212fefe299518426d10812c74ce14d547782 (commit)
via 878a31b83ab5df9441804a67a2f9a16ad615ae84 (commit)
via c846c5417c22b4a3b02122860ce9a6977a6ffa44 (commit)
via ae4e532f39b85f619ff99d8feb91898d8ee741a9 (commit)
via ba27028c44e71d0fa19077209b412538be635c81 (commit)
via 9a7261d103fb09bf87b1f827a733aca3cb87d46c (commit)
via ff502308eef747bad13e1e4c763ca7fc2d820a99 (commit)
via 0e9831ebbe7ff8c719605c36f3c3292db31d28c8 (commit)
via d33133715b3f211459678061e0f2d3c40738c07f (commit)
via a6e58431e3582cc73e5c3e2aa20ee09c5da199a4 (commit)
via f2fa03f0b6156c8a64376d1524fe822e3a74cb16 (commit)
via 869ad53a069b3fe4836d818fe820160e914a017c (commit)
via e601cb8dbb5dae21aa90061d2dbf0e23223186c0 (commit)
via 76b963dc31b5df73631b3689868b991759414ba9 (commit)
via 5fa3d0e12c20cd9d07fc83c98dbd32c64eadca3e (commit)
via cd894167c8d367f7367b790d60c3dd23f740ffdf (commit)
via add399ede802585b4606e6b07d60fe831aa8ec7a (commit)
via 7773035d231a2a55664dbe04baa4744da4ada7a7 (commit)
via 8d160f47d4cd2b6bf265580331215fa9d48a9571 (commit)
via a095d57b989aca503c22bfe2121df389abdbd392 (commit)
via a8e4e8678710187bbd044689a0ed603f5182fda8 (commit)
via 5477d36770d789ce36b2a74acedf64cf63e79460 (commit)
via 905a40f2a1c627edd9bbaf26166311ba7e623d0e (commit)
via c924b23383294fe5b7a21aed00e62fbfd8e88d1c (commit)
via f000b6b54d4abfad7abe39ed0b817a29a1f073f0 (commit)
via 6b78fee487016e4920ab469448c559752523d474 (commit)
via b8b0b9c2b4c0ef559c027033fdcb95e8ff61d9fb (commit)
via e4f44ed5418cca60318c89472defcf24eea93826 (commit)
via 137cfc9037a103e5c5ec814a798dbc56db6ae835 (commit)
via db45e761f5f8c140d80d242033642e2d929a2a2d (commit)
via 36d56446c3758b1d8e6c005fabfe8432b9751131 (commit)
via 2bbe63dbbdda8e75fa60df6ea19f4b0354edebeb (commit)
via 2dbb441195792e760669119ea99e945f66a71a69 (commit)
via c4904fef78c9bae9fdff9aa9852d7b8dca3a9ba8 (commit)
via 4209615da0b85838cc481a7ad602d7783bfdd78a (commit)
via aff266bfb0c5ea0d8553fb249aacd91f4f414476 (commit)
via 30bb4f426cc06433dbfdbad4d6080ff9ab80ca10 (commit)
via f8dfc94a4a5941def5a4624f7755c0478b8d6375 (commit)
via 5bfa13805e9a9ee2585426ddd8807175eac1a6a3 (commit)
via ba7d30083a692af134c9b4c52b92b7856b34096a (commit)
via e18d03ada70eab16d6f5855b1bf95e78167c4c82 (commit)
via 0c40d70c28cda1bc61871cd0e26b74656f45114d (commit)
via 37a27e19be874725ea3d560065e5591a845daa89 (commit)
via 3ea93cc724f87d47c024f895650853f284daf9a1 (commit)
via 5e3bc4cf78dddd89f3a9e4670336b7d46bbb124e (commit)
via c16bfe0dc892c2bd34e88daa12120202bcf9e3eb (commit)
via 6c0febf6e8ca20b9d585d5b5577ee2989464fad0 (commit)
via 716eeec4d6fc5d5db9a8dba8b242fd541380a92f (commit)
via a69acbd2f9dde5eb533b95ef0d538495e733dcd2 (commit)
via 173f076fb4c6d687de433786e0cf1ffd53fb95b2 (commit)
via 88639855c40c5a935126bb44d49883e4f452740b (commit)
via 7174cca8bf02759d3947f2bdc5437e11c362d2a7 (commit)
via 042731613c48d83d96bd3a4911b397977ae783dc (commit)
via 6926ec8a661213e78e73922d093c9883a5d16eb1 (commit)
via 331f01d140ccc6d2f9a37fb483581bfdac1f5cfa (commit)
via 79007cc52b34b9cca6259c86a479ddc5100f15f5 (commit)
via 26a6ff11a1634de2624080eeb424df4d782496f4 (commit)
via 6010b776a5a76c7c9df5025e86c7902992750615 (commit)
via e39b61088924933e1335760d21cfa10e0cfd5425 (commit)
via 88a70cf0dcc476dd911e812eaca03e61274f81ac (commit)
via 421343439bb3852a4aea4d9f9ff945a4b8b6ac9a (commit)
via d365482e0e132c96812b7b99c465582a027035f2 (commit)
via 533c4afdae8ff16eb7d7b1f5529dd572e1098cf3 (commit)
via 21794c5df650ecf28f55ed95cb12d278945ab3a5 (commit)
via bd9b58b2875c100b68e3f246a09975bde0a13e16 (commit)
via 4e973e228e15b24d70b36c18f79180833aff1839 (commit)
via b3a9b362643eddb59ef78ff392cf091d0c199163 (commit)
via 92db29d982892679b7ef097f55975357c5abc759 (commit)
via f8ad67f7166f71b5f78721cc2d7df85c2a859bbc (commit)
via 8dc51055585e92db23cfb97fd280a10eecf3ff7a (commit)
via 18adb5bce11c191f14aa26e58fca838aeb9d80d0 (commit)
via f4e6ab2a98e80e8832b201a6ed2db716bd1d4ee1 (commit)
via ccd941541d51c3a3eb3df162e094637ad8317f63 (commit)
via b25df37f16da91f53b2e068fdd5aeb78e349311b (commit)
via dac912f20c6f315f951979906e79c9a47ea172fe (commit)
via 1dc55b22817baca2b9750082dbf408209affbf02 (commit)
via 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8 (commit)
via d5310a9cec9a3ece6b0cb323ea2a933caca127b9 (commit)
via 564f603d2056a823b3436acf0183a9abeec35ecb (commit)
via 856d60a96bbed5735541659d7c12c51fc055a6cb (commit)
via 792c129a0785c73dd28fd96a8f1439fe6534a3f1 (commit)
via b85908b8e8321ae1b1ab139c7182a5026bc7f73d (commit)
via cd917b3660e1d944d749fb2fb19ce9dfb622c39c (commit)
via cc0c7a0a7b9b4abdb94b7c5a2007334b914218f8 (commit)
via e12dee22bbc97e49c7e5641cf50e39f64e5cbed2 (commit)
via 927c2a94869d76fdbb30018861ba235ddf9389f2 (commit)
via 6443e079d1d0dd3b0de766196a2f9b711bd4f4f5 (commit)
via 78f11bb1db706b793f2b45d94da3c613c2f8ca9e (commit)
via 08d9023ba335f084034ec31cf7e304964b267acb (commit)
via dc7e03f9cca07ef79b5741f4353f570f383dbb9b (commit)
via bc768d296fbd5b86b9d100672792605c79c81739 (commit)
via a9f24702174e022332b528b8d39a21c203b67d3e (commit)
via ef7776ea48e1ddbfb3b727836722cbfc8b65f4fa (commit)
via f098bc21fd0c528fdd55bdbd8f99f6fec36c9222 (commit)
via 607d55c17a1d1becedf6e0fb81673627cab763f8 (commit)
via 903087f3c9e778e762483cf11992e4f5ddc9b7d1 (commit)
via 65bbcd0970063c0923f597ee4413ccbcc969ba58 (commit)
via e8ed9fd77960f2e3c4cfc5948d356b47524d441c (commit)
via 31b16f3568b95073fc15b68e435215572f25c5b8 (commit)
via 6fe2c80c65634db3c5b9ef7fab2e2f48a507fc94 (commit)
via 87ff7feca9801dc6c78a5553c090819a66afe918 (commit)
via 48974a8f8351415321dcabf039296e9e7854fbe1 (commit)
via 652859d50201a5fd7d7ec3bc4266ead82fc86c4b (commit)
via aa70659de52c486695a6fc822b673adf51a2abcf (commit)
via c9e1f35632323f09a1e61f96f88df384e337f885 (commit)
via ad322965f290c5d594e084eb57f9bf16eff87717 (commit)
via ad6e16d25942d9bb308fcc1cf4ec839769956981 (commit)
via 2b861742dc6b8461984a1c1c0437b46a313f672b (commit)
via 3e191cf2889f9ef77561ebac18e1cc019d1bffe4 (commit)
via f0779391ff33e44afac4fc7911400d9b3f345446 (commit)
via a2e1d3f2d9f4bd9680932a46cf2b00c1cd2a1a64 (commit)
via 09a0594ddec459ff3d7bf1e0f0814847d5904883 (commit)
via a25a5fca02c5aca0a29113ca293581b1b48bcfdd (commit)
via 8e118d8e64ff03678c4dd4bdbc6e8e8c64737ac6 (commit)
via 13b8d3383b390dfb30ba39d9f7c69d1c0a7fd572 (commit)
via 7eaf1f34a92aef6e6e82b59e1a8200c858820b77 (commit)
via 45b538d01cc68a759ed8eb1d9e069def986e2ae9 (commit)
via b789222291eeb63126dc1c249ed89d4f4517db63 (commit)
via 6e4caf9dd4f4239776d1e05efbfe13987e5bf9a5 (commit)
via 2548205bf705e3f2d9aeb75e35eb112206476d03 (commit)
via 8399e2b6d96d18197dc27b9f4bee1f5051f6c7af (commit)
via 3c162ca506c8fc08ab93374521807b2132e81c24 (commit)
via 5eb2ff86d745543185551758bf31132596ad20d9 (commit)
via 0de32e7fe21e18069d26e3d282ac8078c852f004 (commit)
via 70cb4d4886e27554e9a9e8751089ba8bd4678701 (commit)
via fb10ec4e31a94ed4c98fa83c5ef3064f8e53a39e (commit)
via 83e71312ca3631e5a8f2364bb8434d69ae000ee1 (commit)
via b55b8b6686cc80eed41793c53d1779f4de3e9e3c (commit)
via 7234eee9cf529a98841ca502dde655bd15f4299f (commit)
via 806865cd2e830785f0d377d759e7475b9e1f640b (commit)
via e7f9ab53656fdfb8a1b27c7056f8dc2c86d8f966 (commit)
via 35d02013f29d19fabf678089ba97db2e67ab3f25 (commit)
via 004823862f69853d3c4acd6f8e0bc60925c25ed1 (commit)
via 1fa69473d881a5c31cd802563f2c398e1f6d1ecb (commit)
via 3baea8cf898f2e9dcd35c88e48999b8112732f9d (commit)
via 08c2f82000bacb76d0602cc9c428637aed0baf21 (commit)
via be570bcaf88318286027da7da40a600cbe4223e9 (commit)
via e5a354694f4d0192132d00e25223d16eb24f25d7 (commit)
via a92495cd29fbe405bef744b98d01ff200caa05c6 (commit)
via f516fc484544b7e08475947d6945bc87636d4115 (commit)
via d509593229c0424fc430766f56d5e5947cb3dda8 (commit)
via f1e9ef1f4906cfc43cf76308675d24a851739105 (commit)
via 96e05ecaa1e2c19c28ff5d1f50efdcff7e3e7d5c (commit)
via c6f04a39f70ad71571779b734e7a3593e6c2f69f (commit)
via 96f973347163030737690174068f51c193a2f4e6 (commit)
via c85c06e93473453547d4a378ebc3c12ef74ae68c (commit)
via 202d9dd4ef7d116f844029a34c36fbdb48921ba0 (commit)
via 18831888667fe9363c22d3d19db9b0f98e56ef70 (commit)
via b0a3df2d384689dad61a47084b5dfa5a00918885 (commit)
via de2886172153001a862453ee4ed73bcd07db2ae0 (commit)
via 58c136da23483524f053d3caa20210941bfa13db (commit)
via cb50f4f6d306ebd40d7ff03b1494dc52b96b191d (commit)
via 7cee8494a729694402e9ce19d257d643888b7a4c (commit)
via 15a69fd77a14825ca0deb1cba52690bf0ea33195 (commit)
via 50e76dc960ad76ec97004832d718cb337390fce8 (commit)
via 3e9a69424117d8f9841d76cf71dab353b6aa5435 (commit)
via 890e0b0b7df76e61c1638744647a793143537722 (commit)
via a6ce7e4332223277ca50cd68a65e7e404621717c (commit)
via d83fc2a12f36818bdd60c87e61d01be7a5dfa4e5 (commit)
via 46efce68984c6ab8c5d02618e2b9ca21bf46de06 (commit)
via 7bea86f90950a22f72d0c6271d7086ab59abdfa0 (commit)
via 1f011110511b06ca175f19fecf0ce522140c6f95 (commit)
via 9e85dc2c98c9fde0f1d73c1d6c0d0d6188b41439 (commit)
via 36622df85e6b1b7ce4fc621fbfaa21d2b35003d8 (commit)
via bf592d0607a639ff454dcdc820a76ae0eaacbbf4 (commit)
via 2ed60b95b8cbdff758e08062fc82c8d8f7b367ac (commit)
via 059fcd024b1611a7c5ca52a6f7055f7c9d14f106 (commit)
via d078381e41cf375a3504655ec921999828c284e2 (commit)
via c6e109f7d68347703baa542d0a913e97394b8057 (commit)
via 9fbb0ae95365974d80a87704f64d6e4907dcb52e (commit)
via 4cb0974a3a642d06513ecc43743109bd92e45a08 (commit)
via 573d2b1fbc3145d18ec71621f56e5c74a328ed3c (commit)
via d831b49beab09fe65b95d39858486824e9b9a4da (commit)
via 2d107bd1f98f6a3aed2120213d713111246e6535 (commit)
via 57e0fe723d9769cb893ae168f7d9374280670c4d (commit)
via 28f38670d37fe76c2481df6b8f82284ce4949de2 (commit)
via 9a2fd23cdd58afe5dbc932dead8f10b0ac22605c (commit)
via b8e5414ee0445b3b0141203470ac07f42d7bbc32 (commit)
via aa32f38e127d1b3b3d4e1349fbfdcac155dc5c5e (commit)
via 40502d5e2c6cbe71143e842ac7908e8a55b62324 (commit)
via c6981a0eed4cb0c68f747fa7f758e53dbfdaf5f7 (commit)
via 385f156f420aa17a1d6f07bcb17a87075f9d4492 (commit)
via 0fe0d44af6e81f2e5230515237fcc84b42f37d85 (commit)
via 60b979b7088f9e282b173c8d71792e2c4b20b57c (commit)
via 0974318566abe08d0702ddd185156842c6642424 (commit)
via 31147f0f82fd1e1b8ead164153599269822517ea (commit)
via 8679b4053024172db8e3d55b7ed50a855efc77c0 (commit)
via 5530bff261fd921726fce87c2d296da5271c639f (commit)
via dd444334eb873bd925639e3141c3a863fad1e47a (commit)
via c1fd5c76dc679f0156748f66f58f3d7220703332 (commit)
via 234e86994e8af3f68d830ec22e9857aa34c78983 (commit)
via 5d3af5f896df69f42590921a44650745411673fa (commit)
via bbb8a8f0ed42862b7e5b54e7611cb0d0e364203b (commit)
via ee424bbc727b754b8030fb2adaa69c578f096de9 (commit)
via bdf19add5fca40dbc91c6ce4c0d87d8bb6080bde (commit)
via cfbb9eada0a6e0ff8a2faead73f870d3ca3400fc (commit)
via 8bca74f57a81fa7e2dc2569692b253dbf19270b4 (commit)
via 34929974f3655f19029d83234e4c5b89b85eaf99 (commit)
via c466b2fb0a4888db16ff0888dc0f8c9875d6f43c (commit)
via e2f99db35b161d951e60aee4728118cf41c3b3d4 (commit)
via f47a693087d2583eee59f97e9b43c40535443688 (commit)
via 9faa2925c3934e3526b5b24694ec0986a6303d42 (commit)
via 469a7104b7231ef26e9f08e85e9f4acf035398c4 (commit)
via 4d7307519b21a0364350cbac4560f6693430cd6b (commit)
via 86f542bb1fecca6e8d4b93a3462649b9dc17805f (commit)
via a12ebb8fdcf246c1a096d62c624185c96f88b186 (commit)
via 0fd48fc7f5406e8cbd8b459a7fc5231053519d3b (commit)
via df8823fdb350670a1e5333b335b59a56e8b09b4c (commit)
via 3e100854e0a85d215d614729f6d429340be1ead2 (commit)
via a146bf1575760b55554d3d77520ea0900b9a4d51 (commit)
via 1deefe2736bcc2b61e07f20ea732d178d84ab772 (commit)
via 6c7d6aae42af702f8b846b3984c09854a5a58539 (commit)
via 09e8ac90d96a468beb509c4da6e9dd74975aa1ad (commit)
via 20419496af5c9374c7858251e1b857413d1f7d17 (commit)
via 3965fb5302e46eb4e7763a328add0a83687a8309 (commit)
via 810c95b11f458b18c196a39cba8f3d47ac1d6cb2 (commit)
via 85da7d68f1eff606608bd46df43a8f57436a0131 (commit)
via d4eda56344420aafa49d1c0d91d1a6a95c7a4707 (commit)
via bd8ac0ce36dee77dc4865239576745db7588db60 (commit)
via 98d515d5e653a75762b5da424555ac301e7f9af5 (commit)
via 4a15ebc6f38bf2b2fe66a069a58f22f89e8ec6fc (commit)
via 8f149bd04986ba2542e86c7ce77e5d0e92df16ea (commit)
via 201672389bfa78c8eb59e122f6f117d1eb0644ed (commit)
via fc882313210b117eeec371b33796a54a4094c1d3 (commit)
via 82d0b7f2ab2fcca6daafdcd5aae06cc0efcd2ffd (commit)
via 2205f81d523ba1175614c6a7ee54904b74ea1a43 (commit)
via a9c8b8f3cd7ae57265efd4e01c51cbb4526c038f (commit)
via da5c7b1faa2363bbca6db163364e6e0073bcc3ab (commit)
via 823f41b44713d161cbba7456914387e323595220 (commit)
via 0e90f478241569b4c63e86eea31553973bdab394 (commit)
via 0232cbe00f15efbe750b21c80973feb220bdc6c2 (commit)
via 67eb8ca010320d7c942ba154d70456d8e9981fa7 (commit)
via 0b722ec0d7813e933db3919df492dccf11a3b4eb (commit)
via b0732ce0447db537b3d643735f16aad09ddd4c7c (commit)
via 1c2b85d7d1c1448d78b8f2a9927b7952ef42e39d (commit)
via 6d3fb27de20503a07333bbc9e053451a80442a41 (commit)
via bef01123fb5dfaee927bc58da465109240811a18 (commit)
via 62794b031c00451e7bec0a0b94b4a2dadb42ae0e (commit)
via 82041829bd8cc7400d0fee9d08e4cf1091c25a70 (commit)
via baa1ddd9057d0f10f85faa67f92e94268189dcd1 (commit)
via 5379cabb61c3daff05c541ab6c9675a8a3ebfed7 (commit)
via 30cbe355d7fcccf3e06472e5fd04c9de870adbe6 (commit)
via 2629140f12accdfa6b4de4f59e5a76b0cc10ecb6 (commit)
via ea8082a263cb6d2b69764da2322b2c483913e5da (commit)
via 6c20066d5613dfda246d598f47b9e779eef9c70b (commit)
via 2aa422106dd081138c633ad2f9a3ede0ebdbd94a (commit)
via 14220fefc56f7970d6b8bfd316c7ee234dd416d4 (commit)
via 34596a05508cee3e48565a4082e9f6e9b815ad41 (commit)
via 6b045bcd1f9613e3835551cdebd2616ea8319a36 (commit)
via 32e13fbffae1a721a44f8b764ecb5ee066e64bf5 (commit)
via 2d46b704c62b47c2313d80b5afafbb180c1b7853 (commit)
via ae8851eea4d83050ad2a8e17fc6b8f0b11364fdd (commit)
via 2078a3a34f198220806f9eabcf6fa7f6f0c9288c (commit)
via 5ee9971dc60341992cf7da807dbec32811dd14d7 (commit)
via 025db8d7a207b39174362981868742f1b80bdf69 (commit)
via 00845ee8d7366b80e23e349b7cf8cd9d52551785 (commit)
via 5d2a22ac8f92c53a89a5f4507ac3946529b9d49e (commit)
via 4323bf82f07269a4f99a951b659749a5a1234ef2 (commit)
via fb95fbd37114f9c8fed764979cc5dc9124e3439c (commit)
via 96e4248d61ccbcd23c7563f0a36d5443b607ea02 (commit)
via ae5693b89371208d5d5d8e884b52362f861c633f (commit)
via 778e6009722520325aaa1efc98240fffc8f6b6d1 (commit)
via 59b176e95540aacd2b0f167cba350d7fa18771db (commit)
via b52e736b9e5079aedf5252f04d019a80292bfc41 (commit)
via 9b0a73f8a2523480f538747d579c940534ac5ac5 (commit)
via 36fea252b03a38a0a86b830bc6a3373a88966413 (commit)
via d4bd5936f52d20ba21865858861dc358f531da04 (commit)
via 19f2ceb60860badfc183f9d4e7d54ac9105ee296 (commit)
via ce6444b69feb909e5c8212e7335eeca14a2b11ea (commit)
via 33ba1827b513aa735a483f6927667a9e872a2207 (commit)
via 23035066d0c07c7b83120d3cba9efc1febe18ff5 (commit)
via 92e4f34d7c74ff7b34599e1d3e8d0622ebc633d6 (commit)
via c2e9d347dcd2fdb0f9092dc4b04579d1c412de46 (commit)
via 90f997d7b29824aa848beb73745e5009c415956d (commit)
via 080d47deb3088adddd96434acfd1c82bd14779c9 (commit)
via 9f5102a30e4cb8da4697b3561206957b9a2aae05 (commit)
via 0758ead7f4044e0867d4f953427b6c10f7afab45 (commit)
via 9ab72138d43cb21a620c021a24e6a248b3dd77ab (commit)
via ffe973e3df51ab36e17087eda23b1042500d39b5 (commit)
via 857d7fbf031d29b5b66ebe38fec725dde35008fd (commit)
via 514b87c307fc834f39f0be155fc9bac9a6508d70 (commit)
via 6d28690c67369927bb0806618f23533a1457eb6f (commit)
via 029be672c7bf1b936859e811a17d45d04215ecd4 (commit)
via 1a0577f9f348a74eacc959e091756f0e7a71accb (commit)
via 5ec876c98d10dfdb0675ffcde709b5ce9871465b (commit)
via 09dc37b8319af571d2e7afb0bf5b6da82cd174f6 (commit)
via 12b24545cb9aeb86229694cda68c71577587f125 (commit)
via 0a14dac0417402193dbcd50cb73360aeef9aabb2 (commit)
via 9cb41a2f06b35290e72eccc8246d8abf57f6e929 (commit)
via 7383457001cd2b6d78ec755f1cf20fa4529a43ab (commit)
via e6c05e218034a214844e1d4ab8b8c1060ec05489 (commit)
via b5735838a44da418d7d66724c8bb53c2d4d1b3c7 (commit)
via e4a823ad343ed077f88da1e581d0486c9b634a35 (commit)
via 6c738ab17c6b6632f1a607cea6cea480fd1c72aa (commit)
via 7eee12217563565248904b7051a6d68f2374ebe0 (commit)
via b5aa1d1b12636ba96e3fff48e27fb6acf6e38333 (commit)
via 2fc0ce200dfa85a5f7baa577750b3c3fe8032f7d (commit)
via d33dd125553aa9702e1a29a6c0e30da9ffffe53b (commit)
via e17ddc8b0f6726a185e982c22dbf787351c22cdd (commit)
via 8960a571bc2db7129d4ce8861f83808fb71dc81c (commit)
via 3ff7531f6be864ca6965a60ef1e2744e945a3569 (commit)
via 786673c6998d886be73950a99a0292344b750cea (commit)
via fe40dade09da3ca910f034e650db2a3d5a93a49b (commit)
via fb8433b523f854b3978ef1c868d573e172a5a76c (commit)
via 5d2f35c760be8fe9aa54f566461ca8a9c5d0db78 (commit)
via 5d8d1e296a4eccc34ab0c19acfa9a4f39a63745c (commit)
via 08dec0f8e0129ed43373dca3a335d752a3fb5e2c (commit)
via 6ed60d209f8c6d7d7d4978b19a2feb6170e2c67a (commit)
via 448686bfad7694acde8b08355dd5921efd4bb2ca (commit)
via 5deb6bbc2df15dc20f36727f0abeaa4d4ad0c17e (commit)
via e44d06354aa0785cf5bd247358e59f7dfe69f5ad (commit)
via 17cd3618c21b12d7867d9e5e4dba81c1cee46cb6 (commit)
via 376b3b0760c5198dbf5ccf740c666ed9c9acaf45 (commit)
via 4beebdd9209a2a75b33068f4d1811b2664b957db (commit)
via db55f102b30e76b72b134cbd77bd183cd01f95c0 (commit)
via cb9f017de088330bd6e4107bad642b5e4ea66ce9 (commit)
via 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb (commit)
via daf78976a3df91027c2a82cd0a794255b60507b0 (commit)
via 291591772d83ff6b5ec9412b03624d2c9f13dc82 (commit)
via 28a43fa59ffd4f772104a76e3636b8c54b329aa3 (commit)
via 9065335de2f05d3bc1d874b933ba80b51feb68f6 (commit)
via caf95c197aa4abb916d8e27d3a5004ebed2e6108 (commit)
via a32568b7b3006315e79ee411b50db8cc4dc435a7 (commit)
via 3c702b8966081eb2d73914754abd247350957bdd (commit)
via c645605f43d14ed37a22369c3f5ac177acdeb7cd (commit)
via 8b1a0145b79147510ccde00b28e5124b8951e4d1 (commit)
via 19bd1a5a0b222ac2473793d87539f76a34cb1bec (commit)
via 27e7210b344cea5c7098bf233481d5f2bf7cac97 (commit)
via 230078e5f45f29fec72e35a77e2fd897fb6d3fb7 (commit)
via 77b42eabd7630b5ba7f5f9fa383de73e0e7fe49d (commit)
via 31a4bc5aacc85f79416c6c4da2e33e642077e9f9 (commit)
via 89f331dff577268f90d885dc871cd2a152940ef8 (commit)
via c6c569cae91e19aa6267081ef9926f52b87d903d (commit)
via 3ac15510bb80c1f7fe76779dfa9ff254eb5cc796 (commit)
via e1c2233fab06d393eed6e16fc09f9bf672963112 (commit)
via 5f8c941e31630163287b5197b6185dae9b03de13 (commit)
via 17553447e571eee4690ad90caf8365977d57c4f0 (commit)
via 5054d0ed85f4645a8019e77c860e7415042e6d05 (commit)
via fb95fe7735c5995f727ea42dfafcad820a3764da (commit)
via 61c303c8f15dce6a985e5d0164a50844b6c64558 (commit)
via 6eb4014782afae7660dbdc291c315a211e5ceed5 (commit)
via bdb56ee0da86148611e50095c9d70dfe4e4f4b7f (commit)
via 17641de21f0430981b554af5595711a3bcf2d699 (commit)
via 15f662d68bbce471df63609cf8deccb280670048 (commit)
via 96c1a3cc7becc69e4bb24cd3e40aaa605e3d90e3 (commit)
via 774a4996dafc7a5809ab0ae08851d412e996e970 (commit)
via c864447b8ec187264fac032be1561c77bb3103b6 (commit)
via 95483cb529b42683002528186eb29d6854e7ce0e (commit)
via b0bc2d360163be4126f6a88b73a3c17a18714736 (commit)
via 772ce4c2888583e2fe1e9959c705608f6d1b7e98 (commit)
via 1b69710afed58b9f7ebff61229f3f29e487f4026 (commit)
via 4d6495aa80132d3a67ad4ef9aa76d75683772a80 (commit)
via 530e62ba1a41f63a8dbcd0838fb5cfa95cb3816a (commit)
via ed7622df90ee865b5be03b0d13ed5977466bebfc (commit)
via 72beaf8964363734d4f5e7b4b11ae7b8b4b5557c (commit)
via b07567633570ebffebb13e92ae71d429967a7cb8 (commit)
via 46cf8771c26b220b60ca8f30332442b5a7dbcbef (commit)
via 593eacf16fe69fade5eeb71704ac25c6e046421b (commit)
via 44f6dc45fffe9a44d8700bbb601dab169568f7ac (commit)
via b40cd6851ace87bc97a095cf76b2a0ddee3666ed (commit)
via 34cb2bf92262addf0fb401365c23773dee3f3e89 (commit)
via c55cdfd6318072c9ae7005298fca07615065c97a (commit)
via be90b92cbe913d2eda9b95f18c099669d0a72a57 (commit)
via 6ce0831748423ab48611785f29ee999beeaf8322 (commit)
via 32b6b02bc06bbf0303639964f3544e9cba4b138d (commit)
via e632c67d9f527acf7cea58af306057e5ba5601a4 (commit)
via 8ea9bddd35ab3a5716a51310ccc4cc6a3d96b572 (commit)
via 318530edbf905ff288f527ef73bb88da4c57b548 (commit)
via 0c8094b0e3e9ecae8fe44cb889b959ed467bac8b (commit)
via cf41749b421bd09330ca5cbec3e3e9a3cdae06d7 (commit)
via 02e63b13f8e523d913de3a516c442677dc15e4c6 (commit)
via 1e45b7161d9405b25072a4b6e979a18fa26981ce (commit)
via e78b80b2a929ab815dee2d4ff6c7e29315a5f2f7 (commit)
via 966b5ee5cf9512da8f4a4ea31f7f67ca3836df26 (commit)
via b95523d5350585a707693d680152903ede08bb0a (commit)
via 58771328093e3bb51463437cae9555da0ed945cc (commit)
via 17e0f403185b1e0136cf1059856c1f30a5bdfdec (commit)
via 1150b0ef4bae79e2ffe254dddd5863690a71d60f (commit)
via 65a2fbba5becb20d276aeb90e5d13d02190828c7 (commit)
via 32bb433cb895fd5013c050612abfe62fb09a689c (commit)
via 525dc51879ae8164ed085f5f6c48f8566853b230 (commit)
via 9a08bfa4933a435a4911cef029e63f57deed0dc3 (commit)
via e1aca7e9404c4064f0125b7596d67cbd8af932e5 (commit)
via f5dc6575c10281b39320167809689df16ae913cf (commit)
via 46dba3a3c60b81e609ee7ac2acc9641a8b62c9f3 (commit)
via 96ee507bbc179b6e3732114dfad67e6c5168c538 (commit)
via 3836cadb5368169a86a27e8af12374c8b715f5a5 (commit)
via 02e9061387eff505391762b5568c2e9700b021e5 (commit)
via b53ecd7c2508d159e92bf890f9d3b2e3747d053b (commit)
via a1fcd146a64717e59311eaeae9b97fb66857e636 (commit)
via 0e4b3df29c59a4b4cf4c75f37fd3c3180332e7fb (commit)
via 13c2eb59454d17b9d00c3cc7205ba19e8b69ec8f (commit)
via 8e0ebda2f002e9ef7530ac16ab7947104767d20d (commit)
via ad4eae2bc3410084b4ea07dd96118c7c1da17a25 (commit)
via 36df68ce895eefd601857fa5d8e1d701137fe218 (commit)
via a9a229b2314c336cf4dd7a257371231530dbae13 (commit)
via 48625915d6898ddca0eba5b3be512b1dcb29487b (commit)
via 0a10b2ed25935e83cd0402ae4df81e6d321d75e8 (commit)
via 78a4e4cd8735ad72dcaafe9701482c34d72fef3e (commit)
via d834a1a1b95f67b2ea2467b75e00fdfe9ac0c763 (commit)
via abd4fe9ffe48402cafdac9c1145f25c43da62332 (commit)
via 2c8b5b294bf5498b9525dcc1dcba7ca1869f01b6 (commit)
via c6aa9a4c2e17c51b28b91cef5b773b935caf317e (commit)
via 7dee66ffe8b7e3f5c49c20608f0f38508d861ced (commit)
via 117645fa11500c467045a380dc41d6999518c5dd (commit)
via 0ca61cffc9fb7f55789f1db88a41a9a5c1f5d34f (commit)
via 83e4b764a988e7774312ff75e0623bcff66f7009 (commit)
via 69bc301dec66ffd80960a45c882fd50c498121cc (commit)
via 2ba2f0269087e5dae4640091e711e4f2964d2fa3 (commit)
via 2f29fad03bb3c9885d0bcf84d39102e6ee902830 (commit)
via a52d10a93a24f736ed4112ea76778a61407a0a03 (commit)
via 123f5a29a7d5c40b0ca7a6ebe8af3b3790c29eca (commit)
via 525ca6149da521a00d02895a5184deef7f8359f9 (commit)
via 889dc09ad441f3e5a3ce2170fba402db258c2a08 (commit)
via 23818a5f2f8e5c68300b11ba8e71178dee8ad794 (commit)
via 949f8945514ac7d422a68f34d7cbf097c864bef8 (commit)
via 57e46e4378c4d8b8dd39cca093500672fa124e42 (commit)
via b89a21c32f1dbea865f632cf517cc335b9b70919 (commit)
via e0098c29f519e84214d012cd5d0ae7f25aa40f73 (commit)
via 977568b91a6595c7d2fd8a618d5f15783962d908 (commit)
via e5e001e23347199841f14f2c0880e2f5f304bd0f (commit)
via 8f73baf041ffda0351e5b9072e1ea43401ce497e (commit)
via c38d780b1390e3dc4c3bf702f6c059a8c3de3406 (commit)
via 5cc65af294ab05bba59614684d71d450eed57c37 (commit)
via 74aa99552605bb7639719501807a13c393e560b9 (commit)
via f950449fc2ffc2e31672ab99d0e432c3b3efc4bc (commit)
via 62c7075662a25c06ee6d877404b8ddad16dd41ee (commit)
via 7c00e40c1c815ea1b8dd13cc718772140cf575dc (commit)
via 96edf56fbab807319a975fe7df264ace0c5dbb37 (commit)
via 1c6a6aa89ebb3b512a09ad19609f3682b198993a (commit)
from f65147ed98354cb39e52568a6ff1f7f98ad885ff (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 525333e187cc4bbbbde288105c9582c1024caa4a
Merge: f65147e 2023dc6
Author: Shane Kerr <shane at isc.org>
Date: Mon Jan 21 20:33:32 2013 +0100
Merge branch 'master' into trac2571_commit_log_fix
-----------------------------------------------------------------------
Summary of changes:
.gitignore | 3 +-
ChangeLog | 136 +++
README | 24 +-
configure.ac | 75 +-
doc/guide/bind10-guide.xml | 110 +-
examples/README | 2 +-
m4macros/ax_boost_for_bind10.m4 | 112 ++
src/bin/auth/auth_messages.mes | 8 +-
src/bin/auth/tests/.gitignore | 2 +
src/bin/auth/tests/Makefile.am | 37 +-
src/bin/auth/tests/auth_srv_unittest.cc | 27 +-
src/bin/auth/tests/gen-query-testdata.py | 98 ++
src/bin/auth/tests/query_inmemory_unittest.cc | 123 --
src/bin/auth/tests/query_unittest.cc | 1108 ++++++++++-------
src/bin/auth/tests/testdata/.gitignore | 10 +
src/bin/auth/tests/testdata/Makefile.am | 7 +-
src/bin/auth/tests/testdata/example-base-inc.zone | 236 ++++
src/bin/auth/tests/testdata/example-base.zone.in | 7 +
.../testdata/example-common-inc-template.zone | 5 +
src/bin/auth/tests/testdata/example-nsec3-inc.zone | 16 +
src/bin/auth/tests/testdata/example-nsec3.zone.in | 8 +
src/bin/auth/tests/testdata/example.zone | 121 --
src/bin/auth/tests/testdata/example.zone.in | 6 +
src/bin/bind10/bind10_messages.mes | 11 +-
src/bin/bind10/bind10_src.py.in | 81 +-
src/bin/bind10/tests/bind10_test.py.in | 97 +-
src/bin/bindctl/bindcmd.py | 12 +-
src/bin/bindctl/run_bindctl.sh.in | 5 +-
src/bin/dbutil/dbutil_messages.mes | 6 +-
src/bin/dbutil/run_dbutil.sh.in | 5 +-
src/bin/dhcp4/config_parser.cc | 1112 ++++++++++++-----
src/bin/dhcp4/config_parser.h | 128 +-
src/bin/dhcp4/ctrl_dhcp4_srv.cc | 3 +-
src/bin/dhcp4/dhcp4.spec | 70 ++
src/bin/dhcp4/dhcp4_messages.mes | 122 +-
src/bin/dhcp4/dhcp4_srv.cc | 403 ++++++-
src/bin/dhcp4/dhcp4_srv.h | 103 +-
src/bin/dhcp4/tests/config_parser_unittest.cc | 609 +++++++++-
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc | 983 ++++++++++++++-
src/bin/dhcp4/tests/dhcp4_unittests.cc | 5 +-
src/bin/dhcp6/config_parser.cc | 1258 ++++++++++++++------
src/bin/dhcp6/config_parser.h | 136 +--
src/bin/dhcp6/ctrl_dhcp6_srv.cc | 4 +-
src/bin/dhcp6/dhcp6.dox | 32 +-
src/bin/dhcp6/dhcp6.spec | 76 ++
src/bin/dhcp6/dhcp6_messages.mes | 87 +-
src/bin/dhcp6/dhcp6_srv.cc | 294 ++++-
src/bin/dhcp6/dhcp6_srv.h | 99 +-
src/bin/dhcp6/tests/config_parser_unittest.cc | 760 ++++++++++--
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc | 561 +++++++--
src/bin/loadzone/.gitignore | 1 +
src/bin/loadzone/b10-loadzone.xml | 18 +-
src/bin/loadzone/loadzone.py.in | 52 +-
src/bin/loadzone/loadzone_messages.mes | 8 -
src/bin/loadzone/run_loadzone.sh.in | 2 +-
src/bin/loadzone/tests/correct/include.db | 4 +-
src/bin/loadzone/tests/correct/known.test.out | 4 +-
src/bin/loadzone/tests/correct/mix1.db | 4 +-
src/bin/loadzone/tests/correct/mix2.db | 4 +-
src/bin/loadzone/tests/correct/ttl1.db | 4 +-
src/bin/loadzone/tests/correct/ttl2.db | 4 +-
src/bin/loadzone/tests/correct/ttlext.db | 4 +-
src/bin/loadzone/tests/loadzone_test.py | 14 +-
src/bin/msgq/Makefile.am | 16 +-
src/bin/msgq/msgq.py.in | 297 ++++-
src/bin/msgq/msgq.spec | 8 +
src/bin/msgq/msgq_messages.mes | 106 ++
src/bin/msgq/run_msgq.sh.in | 18 +-
src/bin/msgq/tests/Makefile.am | 2 +
src/bin/msgq/tests/msgq_test.in | 28 -
src/bin/msgq/tests/msgq_test.py | 112 +-
src/bin/resolver/resolver.h | 6 +-
src/bin/stats/tests/b10-stats-httpd_test.py | 2 +
src/bin/stats/tests/b10-stats_test.py | 7 +-
src/bin/stats/tests/test_utils.py | 37 +-
src/bin/sysinfo/run_sysinfo.sh.in | 9 +-
src/bin/xfrin/tests/xfrin_test.py | 4 +-
src/bin/xfrin/xfrin_messages.mes | 12 +-
src/bin/xfrout/tests/xfrout_test.py.in | 2 +-
src/bin/xfrout/xfrout_messages.mes | 7 +-
src/lib/cc/data.h | 3 +-
src/lib/config/config_data.cc | 2 +-
src/lib/config/config_data.h | 16 +-
src/lib/config/tests/config_data_unittests.cc | 11 +-
src/lib/datasrc/Makefile.am | 1 +
src/lib/datasrc/client.cc | 14 +-
src/lib/datasrc/client.h | 36 +-
src/lib/datasrc/database.cc | 82 +-
src/lib/datasrc/database.h | 33 +-
src/lib/datasrc/datasrc_messages.mes | 21 +-
src/lib/datasrc/rrset_collection_base.cc | 71 ++
src/lib/datasrc/rrset_collection_base.h | 126 ++
src/lib/datasrc/sqlite3_accessor.cc | 20 +-
src/lib/datasrc/sqlite3_accessor.h | 4 +
src/lib/datasrc/tests/Makefile.am | 2 +
src/lib/datasrc/tests/database_unittest.cc | 365 +++++-
.../datasrc/tests/master_loader_callbacks_test.cc | 3 +
.../tests/memory/rdata_serialization_unittest.cc | 24 +-
src/lib/datasrc/tests/sqlite3_accessor_unittest.cc | 48 +
src/lib/datasrc/tests/testdata/checkwarn.zone | 4 +
src/lib/datasrc/tests/testdata/novalidate.zone | 3 +
.../datasrc/tests/zone_finder_context_unittest.cc | 32 +-
src/lib/datasrc/tests/zone_loader_unittest.cc | 217 +++-
src/lib/datasrc/zone.h | 35 +
src/lib/datasrc/zone_loader.cc | 105 +-
src/lib/datasrc/zone_loader.h | 88 +-
src/lib/dhcp/Makefile.am | 13 +-
src/lib/dhcp/duid.cc | 10 +
src/lib/dhcp/duid.h | 5 +-
src/lib/dhcp/hwaddr.cc | 62 +
src/lib/dhcp/hwaddr.h | 67 ++
src/lib/dhcp/libdhcp++.cc | 28 +
src/lib/dhcp/libdhcp++.h | 15 +
src/lib/dhcp/option_definition.cc | 39 +-
src/lib/dhcp/option_definition.h | 15 +-
src/lib/dhcp/pkt4.cc | 136 ++-
src/lib/dhcp/pkt4.h | 60 +-
src/lib/dhcp/tests/Makefile.am | 1 +
src/lib/dhcp/tests/duid_unittest.cc | 12 +-
src/lib/dhcp/tests/hwaddr_unittest.cc | 126 ++
src/lib/dhcp/tests/iface_mgr_unittest.cc | 9 +-
src/lib/dhcp/tests/libdhcp++_unittest.cc | 60 +
src/lib/dhcp/tests/option_definition_unittest.cc | 50 +-
src/lib/dhcp/tests/pkt4_unittest.cc | 47 +-
src/lib/dhcpsrv/Makefile.am | 29 +-
src/lib/dhcpsrv/addr_utilities.cc | 10 +
src/lib/dhcpsrv/addr_utilities.h | 5 +
src/lib/dhcpsrv/alloc_engine.cc | 260 +++-
src/lib/dhcpsrv/alloc_engine.h | 105 +-
src/lib/dhcpsrv/cfgmgr.cc | 158 ++-
src/lib/dhcpsrv/cfgmgr.h | 117 +-
.../dhcpsrv/dhcp_config_parser.h} | 135 ++-
.../dhcp6_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} | 6 +-
src/lib/dhcpsrv/dhcpsrv_log.h | 66 +
src/lib/dhcpsrv/dhcpsrv_messages.mes | 237 ++++
src/lib/dhcpsrv/lease_mgr.cc | 34 +-
src/lib/dhcpsrv/lease_mgr.h | 203 ++--
src/lib/dhcpsrv/lease_mgr_factory.cc | 53 +-
src/lib/dhcpsrv/lease_mgr_factory.h | 35 +-
src/lib/dhcpsrv/memfile_lease_mgr.cc | 120 +-
src/lib/dhcpsrv/memfile_lease_mgr.h | 24 +-
src/lib/dhcpsrv/mysql_lease_mgr.cc | 61 +-
src/lib/dhcpsrv/mysql_lease_mgr.h | 5 +-
src/lib/dhcpsrv/option_space.cc | 71 ++
src/lib/dhcpsrv/option_space.h | 189 +++
src/lib/dhcpsrv/option_space_container.h | 102 ++
src/lib/dhcpsrv/pool.h | 7 +
src/lib/dhcpsrv/subnet.cc | 110 +-
src/lib/dhcpsrv/subnet.h | 165 +--
src/lib/dhcpsrv/tests/Makefile.am | 1 +
src/lib/dhcpsrv/tests/addr_utilities_unittest.cc | 47 +
src/lib/dhcpsrv/tests/alloc_engine_unittest.cc | 553 ++++++++-
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 265 ++++-
.../dhcpsrv/tests/lease_mgr_factory_unittest.cc | 135 ++-
src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 6 +-
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc | 37 +-
src/lib/dhcpsrv/tests/option_space_unittest.cc | 150 +++
src/lib/dhcpsrv/tests/subnet_unittest.cc | 181 ++-
src/lib/{asiolink/io_error.h => dhcpsrv/utils.h} | 33 +-
src/lib/dns/Makefile.am | 12 +-
src/lib/dns/character_string.cc | 145 ---
src/lib/dns/character_string.h | 60 -
.../dns/{python/nsec3hash_python.h => dns_fwd.h} | 59 +-
src/lib/dns/gen-rdatacode.py.in | 9 +-
src/lib/dns/master_lexer.cc | 54 +-
src/lib/dns/master_lexer.h | 97 ++
src/lib/dns/master_lexer_inputsource.cc | 92 +-
src/lib/dns/master_lexer_inputsource.h | 36 +-
src/lib/dns/master_loader.cc | 47 +-
src/lib/dns/master_loader.h | 42 +
src/lib/dns/master_loader_callbacks.h | 6 +-
src/lib/dns/python/Makefile.am | 5 +
src/lib/dns/python/pydnspp.cc | 21 +-
src/lib/dns/python/rrset_collection_python.cc | 453 +++++++
src/lib/dns/python/rrset_collection_python.h | 58 +
src/lib/dns/python/rrset_collection_python_inc.cc | 148 +++
src/lib/dns/python/tests/Makefile.am | 2 +
.../python/tests/rrset_collection_python_test.py | 140 +++
.../dns/python/tests/zone_checker_python_test.py | 179 +++
src/lib/dns/python/zone_checker_python.cc | 229 ++++
.../python/zone_checker_python.h} | 18 +-
src/lib/dns/python/zone_checker_python_inc.cc | 79 ++
src/lib/dns/rdata.h | 2 +-
src/lib/dns/rdata/generic/detail/char_string.cc | 83 +-
src/lib/dns/rdata/generic/detail/char_string.h | 52 +-
src/lib/dns/rdata/generic/detail/lexer_util.h | 70 ++
src/lib/dns/rdata/generic/detail/txt_like.h | 10 +-
src/lib/dns/rdata/generic/hinfo_13.cc | 150 ++-
src/lib/dns/rdata/generic/hinfo_13.h | 38 +-
src/lib/dns/rdata/generic/naptr_35.cc | 273 +++--
src/lib/dns/rdata/generic/naptr_35.h | 35 +-
src/lib/dns/rdata/generic/soa_6.cc | 117 +-
src/lib/dns/rrclass-placeholder.h | 55 +-
src/lib/dns/rrclass.cc | 17 +-
src/lib/dns/rrcollator.cc | 2 +-
src/lib/dns/rrparamregistry-placeholder.cc | 35 +-
src/lib/dns/rrparamregistry.h | 45 +-
src/lib/dns/rrset_collection.cc | 128 ++
src/lib/dns/rrset_collection.h | 172 +++
src/lib/dns/rrset_collection_base.h | 197 +++
src/lib/dns/rrttl.h | 2 +-
src/lib/dns/rrtype.cc | 7 +-
src/lib/dns/tests/Makefile.am | 3 +-
src/lib/dns/tests/character_string_unittest.cc | 97 --
.../dns/tests/master_lexer_inputsource_unittest.cc | 51 +-
src/lib/dns/tests/master_lexer_unittest.cc | 81 +-
src/lib/dns/tests/master_loader_unittest.cc | 72 +-
src/lib/dns/tests/rdata_char_string_unittest.cc | 137 ++-
src/lib/dns/tests/rdata_hinfo_unittest.cc | 25 +-
src/lib/dns/tests/rdata_naptr_unittest.cc | 76 +-
src/lib/dns/tests/rdata_soa_unittest.cc | 160 ++-
src/lib/dns/tests/rdata_txt_like_unittest.cc | 20 +-
src/lib/dns/tests/rdata_unittest.cc | 3 +-
src/lib/dns/tests/rdata_unittest.h | 1 +
src/lib/dns/tests/rrclass_unittest.cc | 10 +-
src/lib/dns/tests/rrset_collection_unittest.cc | 246 ++++
src/lib/dns/tests/zone_checker_unittest.cc | 352 ++++++
src/lib/dns/zone_checker.cc | 196 +++
src/lib/dns/zone_checker.h | 162 +++
src/lib/log/log_messages.mes | 4 +-
src/lib/log/logger_manager_impl.cc | 10 +
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/bind10/component.py | 10 +-
src/lib/python/isc/bind10/special_component.py | 15 +-
src/lib/python/isc/bind10/tests/component_test.py | 32 +-
src/lib/python/isc/config/cfgmgr.py | 1 +
src/lib/python/isc/datasrc/client_inc.cc | 43 +-
src/lib/python/isc/datasrc/client_python.cc | 32 +
src/lib/python/isc/datasrc/datasrc.cc | 28 +-
src/lib/python/isc/datasrc/tests/.gitignore | 1 +
src/lib/python/isc/datasrc/tests/datasrc_test.py | 66 +-
.../python/isc/datasrc/tests/zone_loader_test.py | 34 +-
src/lib/python/isc/datasrc/updater_inc.cc | 36 +
src/lib/python/isc/datasrc/updater_python.cc | 154 +++
src/lib/python/isc/datasrc/updater_python.h | 2 +-
src/lib/python/isc/datasrc/zone_loader_inc.cc | 60 +
src/lib/python/isc/datasrc/zone_loader_python.cc | 16 +
src/lib/python/isc/ddns/libddns_messages.mes | 2 +-
src/lib/python/isc/log/log.cc | 2 +-
src/lib/python/isc/log_messages/Makefile.am | 2 +
src/lib/python/isc/log_messages/msgq_messages.py | 1 +
src/lib/python/isc/statistics/Makefile.am | 9 +
src/lib/python/isc/statistics/__init__.py | 1 +
src/lib/python/isc/statistics/counters.py | 403 +++++++
.../python/isc/statistics}/tests/Makefile.am | 15 +-
.../python/isc/statistics/tests/counters_test.py | 416 +++++++
.../isc/statistics/tests/testdata/test_spec1.spec | 26 +
.../isc/statistics/tests/testdata/test_spec2.spec | 187 +++
.../isc/statistics/tests/testdata/test_spec3.spec | 382 ++++++
src/lib/resolve/resolve_messages.mes | 6 +-
src/lib/server_common/server_common_messages.mes | 4 +-
src/lib/testutils/dnsmessage_test.h | 3 +-
src/lib/testutils/srv_test.cc | 37 +-
src/lib/testutils/srv_test.h | 10 +-
tests/lettuce/features/msgq.feature | 18 +
tests/system/ifconfig.sh | 4 +-
tests/tools/perfdhcp/perf_pkt4.cc | 2 +-
tests/tools/perfdhcp/test_control.cc | 20 +-
tests/tools/perfdhcp/tests/.gitignore | 5 +
.../tools/perfdhcp/tests/test_control_unittest.cc | 3 -
260 files changed, 19224 insertions(+), 4028 deletions(-)
create mode 100644 m4macros/ax_boost_for_bind10.m4
create mode 100755 src/bin/auth/tests/gen-query-testdata.py
delete mode 100644 src/bin/auth/tests/query_inmemory_unittest.cc
create mode 100644 src/bin/auth/tests/testdata/example-base-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-base.zone.in
create mode 100644 src/bin/auth/tests/testdata/example-common-inc-template.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3-inc.zone
create mode 100644 src/bin/auth/tests/testdata/example-nsec3.zone.in
delete mode 100644 src/bin/auth/tests/testdata/example.zone
create mode 100644 src/bin/auth/tests/testdata/example.zone.in
create mode 100644 src/bin/msgq/msgq.spec
create mode 100644 src/bin/msgq/msgq_messages.mes
delete mode 100755 src/bin/msgq/tests/msgq_test.in
create mode 100644 src/lib/datasrc/rrset_collection_base.cc
create mode 100644 src/lib/datasrc/rrset_collection_base.h
create mode 100644 src/lib/datasrc/tests/testdata/checkwarn.zone
create mode 100644 src/lib/datasrc/tests/testdata/novalidate.zone
create mode 100644 src/lib/dhcp/hwaddr.cc
create mode 100644 src/lib/dhcp/hwaddr.h
create mode 100644 src/lib/dhcp/tests/hwaddr_unittest.cc
copy src/{bin/dhcp6/config_parser.h => lib/dhcpsrv/dhcp_config_parser.h} (55%)
copy src/{bin/dhcp6/dhcp6_log.cc => lib/dhcpsrv/dhcpsrv_log.cc} (86%)
create mode 100644 src/lib/dhcpsrv/dhcpsrv_log.h
create mode 100644 src/lib/dhcpsrv/dhcpsrv_messages.mes
create mode 100644 src/lib/dhcpsrv/option_space.cc
create mode 100644 src/lib/dhcpsrv/option_space.h
create mode 100644 src/lib/dhcpsrv/option_space_container.h
create mode 100644 src/lib/dhcpsrv/tests/option_space_unittest.cc
copy src/lib/{asiolink/io_error.h => dhcpsrv/utils.h} (52%)
delete mode 100644 src/lib/dns/character_string.cc
delete mode 100644 src/lib/dns/character_string.h
copy src/lib/dns/{python/nsec3hash_python.h => dns_fwd.h} (52%)
create mode 100644 src/lib/dns/python/rrset_collection_python.cc
create mode 100644 src/lib/dns/python/rrset_collection_python.h
create mode 100644 src/lib/dns/python/rrset_collection_python_inc.cc
create mode 100644 src/lib/dns/python/tests/rrset_collection_python_test.py
create mode 100644 src/lib/dns/python/tests/zone_checker_python_test.py
create mode 100644 src/lib/dns/python/zone_checker_python.cc
copy src/lib/{python/isc/util/cio/socketsession_python.h => dns/python/zone_checker_python.h} (74%)
create mode 100644 src/lib/dns/python/zone_checker_python_inc.cc
create mode 100644 src/lib/dns/rdata/generic/detail/lexer_util.h
create mode 100644 src/lib/dns/rrset_collection.cc
create mode 100644 src/lib/dns/rrset_collection.h
create mode 100644 src/lib/dns/rrset_collection_base.h
delete mode 100644 src/lib/dns/tests/character_string_unittest.cc
create mode 100644 src/lib/dns/tests/rrset_collection_unittest.cc
create mode 100644 src/lib/dns/tests/zone_checker_unittest.cc
create mode 100644 src/lib/dns/zone_checker.cc
create mode 100644 src/lib/dns/zone_checker.h
create mode 100644 src/lib/python/isc/log_messages/msgq_messages.py
create mode 100644 src/lib/python/isc/statistics/Makefile.am
create mode 100644 src/lib/python/isc/statistics/__init__.py
create mode 100644 src/lib/python/isc/statistics/counters.py
copy src/{bin/xfrin => lib/python/isc/statistics}/tests/Makefile.am (75%)
create mode 100644 src/lib/python/isc/statistics/tests/counters_test.py
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
create mode 100644 src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
create mode 100644 tests/lettuce/features/msgq.feature
-----------------------------------------------------------------------
diff --git a/.gitignore b/.gitignore
index dd6903c..7bc41b4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -34,5 +34,6 @@ TAGS
/all.info
/coverage-cpp-html
/dns++.pc
-/report.info
+/local.zone.sqlite3
/logger_lockfile
+/report.info
diff --git a/ChangeLog b/ChangeLog
index 4c268ae..cd627c9 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,139 @@
+551. [bug] shane
+ Kill msgq if we cannot connect to it on startup.
+ When the boss process was unable to connect to the msgq, it would
+ exit. However, it would leave the msgq process running. This has
+ been fixed, and the msgq is now stopped in this case.
+ (Trac #2608, git 016925ef2437e0396127e135c937d3a55539d224)
+
+550. [func] tomek
+ b10-dhcp4: The DHCPv4 server now generates a server identifier
+ the first time it is run. The identifier is preserved in a file
+ across server restarts.
+ b10-dhcp6: The server identifier is now preserved in a file across
+ server restarts.
+ (Trac #2597, git fa342a994de5dbefe32996be7eebe58f6304cff7)
+
+549. [func] tomek
+ b10-dhcp6: It is now possible to specify that a configured subnet
+ is reachable locally over specified interface (see "interface"
+ parameter in Subnet6 configuration).
+ (Trac #2596, git a70f6172194a976b514cd7d67ce097bbca3c2798)
+
+548. [func] vorner
+ The message queue daemon now appears on the bus. This has two
+ effects, one is it obeys logging configuration and logs to the
+ correct place like the rest of the modules. The other is it
+ appears in bindctl as module (but it doesn't have any commands or
+ configuration yet).
+ (Trac #2582, git ced31d8c5a0f2ca930b976d3caecfc24fc04634e)
+
+547. [func]* vorner
+ The b10-loadzone now performs more thorough sanity check on the
+ loaded data. Some of the checks are now fatal and zone failing
+ them will be rejected.
+ (Trac #2436, git 48d999f1cb59f308f9f30ba2639521d2a5a85baa)
+
+546. [func] marcin
+ DHCP option definitions can be now created using the
+ Configuration Manager. The option definition specifies
+ the option code, name and the types of the data being
+ carried by the option. The Configuration Manager
+ reports an error on attempt to override standard DHCP
+ option definition.
+ (Trac #2317, git 71e25eb81e58a695cf3bad465c4254b13a50696e)
+
+545. [func] jinmei
+ libdns++: the SOA Rdata class now uses the generic lexer in
+ constructors from text. This means that the MNAME and RNAME of an
+ SOA RR in a zone file can now be non absolute (the origin name
+ in that context will be used), e.g., when loaded by b10-loadzone.
+ (Trac #2500, git 019ca218027a218921519f205139b96025df2bb5)
+
+544. [func] tomek
+ b10-dhcp4: Allocation engine support for IPv4 added. Currently
+ supported operations are server selection (Discover/Offer),
+ address assignment (Request/Ack), address renewal (Request/Ack),
+ and address release (Release). Expired leases can be reused.
+ Some options (e.g. Router Option) are still hardcoded, so the
+ DHCPv4 server is not yet usable, although its address allocation
+ is operational.
+ (Trac #2320, git 60606cabb1c9584700b1f642bf2af21a35c64573)
+
+543. [func]* jelte
+ When calling getFullConfig() as a module, , the configuration is now
+ returned as properly-structured JSON. Previously, the structure had
+ been flattened, with all data being labelled by fully-qualified element
+ names.
+ (Trac #2619, git bed3c88c25ea8f7e951317775e99ebce3340ca22)
+
+542. [func] marcin
+ Created OptionSpace and OptionSpace6 classes to represent DHCP
+ option spaces. The option spaces are used to group instances
+ and definitions of options having uniqe codes. A special type
+ of option space is the so-called "vendor specific option space"
+ which groups sub-options sent within Vendor Encapsulated Options.
+ The new classes are not used yet but they will be used once
+ the creation of option spaces by configuration manager is
+ implemented.
+ (Trac #2313, git 37a27e19be874725ea3d560065e5591a845daa89)
+
+541. [func] marcin
+ Added routines to search for configured DHCP options and their
+ definitions using name of the option space they belong to.
+ New routines are called internally from the DHCPv4 and DHCPv6
+ servers code.
+ (Trac #2315, git 741fe7bc96c70df35d9a79016b0aa1488e9b3ac8)
+
+540. [func] marcin
+ DHCP Option values can be now specified using a string of
+ tokens separated with comma sign. Subsequent tokens are used
+ to set values for corresponding data fields in a particular
+ DHCP option. The format of the token matches the data type
+ of the corresponding option field: e.g. "192.168.2.1" for IPv4
+ address, "5" for integer value etc.
+ (Trac #2545, git 792c129a0785c73dd28fd96a8f1439fe6534a3f1)
+
+539. [func] stephen
+ Add logging to the DHCP server library.
+ (Trac #2524, git b55b8b6686cc80eed41793c53d1779f4de3e9e3c)
+
+538. [bug] muks
+ Added escaping of special characters (double-quotes, semicolon,
+ backslash, etc.) in text-like RRType's toText() implementation.
+ Without this change, some TXT and SPF RDATA were incorrectly
+ stored in SQLite3 datasource as they were not escaped.
+ (Trac #2535, git f516fc484544b7e08475947d6945bc87636d4115)
+
+537. [func] tomek
+ b10-dhcp6: Support for RELEASE message has been added. Clients
+ are now able to release their non-temporary IPv6 addresses.
+ (Trac #2326, git 0974318566abe08d0702ddd185156842c6642424)
+
+536. [build] jinmei
+ Detect a build issue on FreeBSD with g++ 4.2 and Boost installed via
+ FreeBSD ports at ./configure time. This seems to be a bug of
+ FreeBSD ports setup and has been reported to the maintainer:
+ http://www.freebsd.org/cgi/query-pr.cgi?pr=174753
+ Until it's fixed, you need to build BIND 10 for FreeBSD that has
+ this problem with specifying --without-werror, with clang++
+ (development version), or with manually extracted Boost header
+ files (no compiled Boost library is necessary).
+ (Trac #1991, git 6b045bcd1f9613e3835551cdebd2616ea8319a36)
+
+535. [bug] jelte
+ The log4cplus internal logging mechanism has been disabled, and no
+ output from the log4cplus library itself should be printed to
+ stderr anymore. This output can be enabled by using the
+ compile-time option --enable-debug.
+ (Trac #1081, git db55f102b30e76b72b134cbd77bd183cd01f95c0)
+
+534. [func]* vorner
+ The b10-msgq now uses the same logging format as the rest
+ of the system. However, it still doesn't obey the common
+ configuration, as due to technical issues it is not able
+ to read it yet.
+ (git 9e6e821c0a33aab0cd0e70e51059d9a2761f76bb)
+
bind10-1.0.0-beta released on December 20, 2012
533. [build]* jreed
diff --git a/README b/README
index fdc11a1..306b7d8 100644
--- a/README
+++ b/README
@@ -7,16 +7,20 @@ DHCP. BIND 10 is written in C++ and Python and provides a modular
environment for serving, maintaining, and developing DNS and DHCP.
This release includes the bind10 master process, b10-msgq message
-bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
-backends), b10-resolver recursive or forwarding DNS server, b10-cmdctl
-remote control daemon, b10-cfgmgr configuration manager, b10-xfrin
-AXFR inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
-secondary manager, b10-stats statistics collection and reporting
-daemon, b10-stats-httpd for HTTP access to XML-formatted stats,
-b10-host DNS lookup utility, and a new libdns++ library for C++
-with a python wrapper. BIND 10 also provides experimental DHCPv4
-and DHCPv6 servers, b10-dhcp4 and b10-dhcp6, a portable DHCP library,
-libdhcp++, and a DHCP benchmarking tool, perfdhcp.
+bus, b10-cmdctl remote control daemon, b10-cfgmgr configuration
+manager, b10-stats statistics collection and reporting daemon, and
+b10-stats-httpd for HTTP access to XML-formatted stats.
+
+For DNS services, it provides the b10-auth authoritative DNS server
+(with SQLite3 and in-memory backends), b10-resolver recursive or
+forwarding DNS server, b10-xfrin IXFR/AXFR inbound service, b10-xfrout
+outgoing IXFR/AXFR service, b10-zonemgr secondary manager, libdns++
+library for C++ with a python wrapper, and many tests and example
+programs.
+
+BIND 10 also provides experimental DHCPv4 and DHCPv6 servers,
+b10-dhcp4 and b10-dhcp6, a portable DHCP library, libdhcp++, and
+a DHCP benchmarking tool, perfdhcp.
Documentation is included with the source. See doc/guide/bind10-guide.txt
(or bind10-guide.html) for installation instructions. The
diff --git a/configure.ac b/configure.ac
index 6a88f4e..d8bbef5 100644
--- a/configure.ac
+++ b/configure.ac
@@ -166,8 +166,6 @@ fi
fi dnl GXX = yes
-AM_CONDITIONAL(GCC_WERROR_OK, test $werror_ok = 1)
-
# allow building programs with static link. we need to make it selective
# because loadable modules cannot be statically linked.
AC_ARG_ENABLE([static-link],
@@ -284,7 +282,10 @@ AC_SUBST(PYTHON_LOGMSGPKG_DIR)
# This is python package paths commonly used in python tests. See
# README of log_messages for why it's included.
-COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python"
+# lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+COMMON_PYTHON_PATH="\$(abs_top_builddir)/src/lib/python/isc/log_messages:\$(abs_top_srcdir)/src/lib/python:\$(abs_top_builddir)/src/lib/python:\$(abs_top_builddir)/src/lib/dns/python/.libs"
AC_SUBST(COMMON_PYTHON_PATH)
# Check for python development environments
@@ -838,57 +839,22 @@ LIBS=$LIBS_SAVED
#
# Configure Boost header path
#
-# If explicitly specified, use it.
-AC_ARG_WITH([boost-include],
- AC_HELP_STRING([--with-boost-include=PATH],
- [specify exact directory for Boost headers]),
- [boost_include_path="$withval"])
-# If not specified, try some common paths.
-if test -z "$with_boost_include"; then
- boostdirs="/usr/local /usr/pkg /opt /opt/local"
- for d in $boostdirs
- do
- if test -f $d/include/boost/shared_ptr.hpp; then
- boost_include_path=$d/include
- break
- fi
- done
+AX_BOOST_FOR_BIND10
+# Boost offset_ptr is required in one library and not optional right now, so
+# we unconditionally fail here if it doesn't work.
+if test "$BOOST_OFFSET_PTR_FAILURE" = "yes"; then
+ AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])
fi
-CPPFLAGS_SAVES="$CPPFLAGS"
-if test "${boost_include_path}" ; then
- BOOST_INCLUDES="-I${boost_include_path}"
- CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+
+# There's a known bug in FreeBSD ports for Boost that would trigger a false
+# warning in build with g++ and -Werror (we exclude clang++ explicitly to
+# avoid unexpected false positives).
+if test "$BOOST_NUMERIC_CAST_WOULDFAIL" = "yes" -a X"$werror_ok" = X1 -a $CLANGPP = "no"; then
+ AC_MSG_ERROR([Failed to compile a required header file. If you are using FreeBSD and Boost installed via ports, retry with specifying --without-werror. See the ChangeLog entry for Trac no. 1991 for more details.])
fi
-AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
- AC_MSG_ERROR([Missing required header files.]))
-
-# Detect whether Boost tries to use threads by default, and, if not,
-# make it sure explicitly. In some systems the automatic detection
-# may depend on preceding header files, and if inconsistency happens
-# it could lead to a critical disruption.
-AC_MSG_CHECKING([whether Boost tries to use threads])
-AC_TRY_COMPILE([
-#include <boost/config.hpp>
-#ifdef BOOST_HAS_THREADS
-#error "boost will use threads"
-#endif],,
-[AC_MSG_RESULT(no)
- CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
-[AC_MSG_RESULT(yes)])
-
-# Boost offset_ptr is required in one library (not optional right now), and
-# it's known it doesn't compile on some platforms, depending on boost version,
-# its local configuration, and compiler.
-AC_MSG_CHECKING([Boost offset_ptr compiles])
-AC_TRY_COMPILE([
-#include <boost/interprocess/offset_ptr.hpp>
-],,
-[AC_MSG_RESULT(yes)],
-[AC_MSG_RESULT(no)
- AC_MSG_ERROR([Failed to compile a required header file. Try upgrading Boost to 1.44 or higher (when using clang++) or specifying --without-werror. See the ChangeLog entry for Trac no. 2147 for more details.])])
-CPPFLAGS="$CPPFLAGS_SAVES $CPPFLAGS_BOOST_THREADCONF"
-AC_SUBST(BOOST_INCLUDES)
+# Add some default CPP flags needed for Boost, identified by the AX macro.
+CPPFLAGS="$CPPFLAGS $CPPFLAGS_BOOST_THREADCONF"
# I can't get some of the #include <asio.hpp> right without this
# TODO: find the real cause of asio/boost wanting pthreads
@@ -1251,6 +1217,8 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/server_common/tests/Makefile
src/lib/python/isc/sysinfo/Makefile
src/lib/python/isc/sysinfo/tests/Makefile
+ src/lib/python/isc/statistics/Makefile
+ src/lib/python/isc/statistics/tests/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
@@ -1354,10 +1322,12 @@ AC_OUTPUT([doc/version.ent
src/bin/usermgr/run_b10-cmdctl-usermgr.sh
src/bin/usermgr/b10-cmdctl-usermgr.py
src/bin/msgq/msgq.py
- src/bin/msgq/tests/msgq_test
src/bin/msgq/run_msgq.sh
src/bin/auth/auth.spec.pre
src/bin/auth/spec_config.h.pre
+ src/bin/auth/tests/testdata/example.zone
+ src/bin/auth/tests/testdata/example-base.zone
+ src/bin/auth/tests/testdata/example-nsec3.zone
src/bin/dhcp4/spec_config.h.pre
src/bin/dhcp6/spec_config.h.pre
src/bin/tests/process_rename_test.py
@@ -1420,7 +1390,6 @@ AC_OUTPUT([doc/version.ent
chmod +x src/bin/sysinfo/run_sysinfo.sh
chmod +x src/bin/usermgr/run_b10-cmdctl-usermgr.sh
chmod +x src/bin/msgq/run_msgq.sh
- chmod +x src/bin/msgq/tests/msgq_test
chmod +x src/lib/dns/gen-rdatacode.py
chmod +x src/lib/log/tests/console_test.sh
chmod +x src/lib/log/tests/destination_test.sh
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index 81ef868..5384d14 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1872,7 +1872,7 @@ tsig_keys/keys[0] "example.key.:c2VjcmV0" string (modified)
Each rule is a name-value mapping (a dictionary, in the JSON
terminology). Each rule must contain exactly one mapping called
"action", which describes what should happen if the rule applies.
- There may be more mappings, calld matches, which describe the
+ There may be more mappings, called matches, which describe the
conditions under which the rule applies.
</para>
@@ -3343,23 +3343,21 @@ then change those defaults with config set Resolver/forward_addresses[0]/address
<note>
<para>
- As of November 2012, the DHCPv4 component is a
- skeleton server. That means that while it is capable of
- performing DHCP configuration, it is not fully functional.
- In particular, it does not have a functional lease
- database. This means that they will assign the same, fixed,
- hardcoded addresses to any client that will ask. See <xref
- linkend="dhcp4-limit"/> for a
- detailed description.
+ As of January 2013, the DHCPv4 component is a work in progress.
+ That means that while it is capable of performing DHCP configuration,
+ it is not fully functional. The server is able to offer,
+ assign, renew, release and reuse expired leases, but some of the
+ options are not configurable yet. In particular Router option is hardcoded.
+ This means that the server is not really usable in actual deployments
+ yet. See <xref linkend="dhcp4-limit"/> for a detailed description.
</para>
</note>
<section id="dhcp4-usage">
<title>DHCPv4 Server Usage</title>
<para>BIND 10 has provided the DHCPv4 server component since December
- 2011. It is a skeleton server and can be described as an early
- prototype that is not fully functional yet. It is mature enough
- to conduct first tests in lab environment, but it has
+ 2011. It is current experimental implementation and is not fully functional
+ yet. It is mature enough to conduct tests in lab environment, but it has
significant limitations. See <xref linkend="dhcp4-limit"/> for
details.
</para>
@@ -3480,24 +3478,41 @@ Dhcp4/subnet4 [] list (default)</screen>
</para>
<para>
- 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
+ Note: Although configuration is now accepted, some parts of it is not internally used
+ by they server yet. Address pools are used, but option definitons are not.
+ The only way to alter some options (e.g. Router Option or DNS servers and Domain name)
+ is to modify source code. To do so, please edit
src/bin/dhcp6/dhcp4_srv.cc file, modify the following parameters and
recompile:
<screen>
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
-const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
+const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";</screen>
Lease database and configuration support is planned for end of 2012.
</para>
</section>
+ <section id="dhcp4-serverid">
+ <title>Server Identifier in DHCPv4</title>
+ <para>The DHCPv4 protocol uses a "server identifier" for clients to be able
+ to discriminate between several servers present on the same link: this
+ value is an IPv4 address of the server. When started for the first time,
+ the DHCPv4 server will choose one of its IPv4 addresses as its server-id,
+ and store the chosen value to a file. (The file is named b10-dhcp4-serverid and is
+ stored in the "local state directory". This is set during installation
+ when "configure" is run, and can be changed by using "--localstatedir"
+ on the "configure" command line.) That file will be read by the server
+ and the contained value used whenever the server is subsequently started.
+ </para>
+ <para>
+ It is unlikely that this parameter needs to be changed. If such a need
+ arises, please stop the server, edit the file and restart the server.
+ It is a text file that should contain an IPv4 address. Spaces are
+ ignored. No extra characters are allowed in this file.
+ </para>
+ </section>
+
<section id="dhcp4-std">
<title>Supported standards</title>
<para>The following standards and draft standards are currently
@@ -3505,7 +3520,7 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
<itemizedlist>
<listitem>
<simpara>RFC2131: Supported messages are DISCOVER, OFFER,
- REQUEST, and ACK.</simpara>
+ REQUEST, ACK, NAK, RELEASE.</simpara>
</listitem>
<listitem>
<simpara>RFC2132: Supported options are: PAD (0),
@@ -3513,6 +3528,10 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
Domain Name (15), DNS Servers (6), IP Address Lease Time
(51), Subnet mask (1), and Routers (3).</simpara>
</listitem>
+ <listitem>
+ <simpara>RFC6842: Server responses include client-id option
+ if client sent it in its message.</simpara>
+ </listitem>
</itemizedlist>
</section>
@@ -3533,20 +3552,6 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
communication).</simpara>
</listitem>
<listitem>
- <simpara><command>b10-dhcp4</command> provides a single,
- fixed, hardcoded lease to any client that asks. There is
- no lease manager implemented. If two clients request
- addresses, they will both get the same fixed
- address.</simpara>
- </listitem>
- <listitem>
- <simpara><command>b10-dhcp4</command> does not support any
- configuration mechanisms yet. The whole configuration is
- currently hardcoded. The only way to tweak configuration
- is to directly modify source code. See see <xref
- linkend="dhcp4-config"/> for details.</simpara>
- </listitem>
- <listitem>
<simpara>Upon start, the server will open sockets on all
interfaces that are not loopback, are up and running and
have IPv4 address.</simpara>
@@ -3574,9 +3579,9 @@ const std::string HARDCODED_SERVER_ID = "192.0.2.1";</screen>
sending ICMP echo request.</simpara>
</listitem>
<listitem>
- <simpara>Address renewal (RENEW), rebinding (REBIND),
- confirmation (CONFIRM), duplication report (DECLINE) and
- release (RELEASE) are not supported yet.</simpara>
+ <simpara>Address rebinding (REQUEST/Rebinding), confirmation
+ (CONFIRM) and duplication report (DECLINE) are not supported
+ yet.</simpara>
</listitem>
<listitem>
<simpara>DNS Update is not supported yet.</simpara>
@@ -3852,6 +3857,31 @@ Dhcp6/subnet6 [] list (default)</screen>
</note>
</section>
+ <section id="dhcp6-serverid">
+ <title>Server Identifier in DHCPv6</title>
+ <para>The DHCPv6 protocol uses a "server identifier" (also known
+ as a DUID) for clients to be able to discriminate between several
+ servers present on the same link. There are several types of
+ DUIDs defined, but RFC 3315 instructs servers to use DUID-LLT if
+ possible. This format consists of a link-layer (MAC) address and a
+ timestamp. When started for the first time, the DHCPv6 server will
+ automatically generate such a DUID and store the chosen value to
+ a file (The file is named b10-dhcp6-serverid and is stored in the
+ "local state directory". This is set during installation when
+ "configure" is run, and can be changed by using "--localstatedir"
+ on the "configure" command line.) That file will be read by the server
+ and the contained value used whenever the server is subsequently started.
+ </para>
+ <para>
+ It is unlikely that this parameter needs to be changed. If such a need
+ arises, please stop the server, edit the file and start the server
+ again. It is a text file that contains double digit hexadecimal values
+ separated by colons. This format is similar to typical MAC address
+ format. Spaces are ignored. No extra characters are allowed in this
+ file.
+ </para>
+ </section>
+
<section id="dhcp6-std">
<title>Supported DHCPv6 Standards</title>
<para>The following standards and draft standards are currently
@@ -3871,7 +3901,7 @@ Dhcp6/subnet6 [] list (default)</screen>
<section id="dhcp6-limit">
<title>DHCPv6 Server Limitations</title>
<para> These are the current limitations and known problems
- with the the DHCPv6 server
+ with the DHCPv6 server
software. Most of them are reflections of the early stage of
development and should be treated as <quote>not implemented
yet</quote>, rather than actual limitations.</para>
@@ -4156,7 +4186,7 @@ specify module-wide logging and see what appears...
If there are multiple logger specifications in the
configuration that might match a particular logger, the
specification with the more specific logger name takes
- precedence. For example, if there are entries for for
+ precedence. For example, if there are entries for
both <quote>*</quote> and <quote>Resolver</quote>, the
resolver module — and all libraries it uses —
will log messages according to the configuration in the
diff --git a/examples/README b/examples/README
index 08f53fa..aa5f3c9 100644
--- a/examples/README
+++ b/examples/README
@@ -15,7 +15,7 @@ the "m4" subdirectory as a template for your own project. The key is
to call the AX_ISC_BIND10 function (as the sample configure.ac does)
from your configure.ac. Then it will check the availability of
necessary stuff and set some corresponding AC variables. You can then
-use the resulting variables in your Makefile.in or Makefile.ac.
+use the resulting variables in your Makefile.in or Makefile.am.
If you use automake, don't forget adding the following line to the top
level Makefile.am:
diff --git a/m4macros/ax_boost_for_bind10.m4 b/m4macros/ax_boost_for_bind10.m4
new file mode 100644
index 0000000..1ce367e
--- /dev/null
+++ b/m4macros/ax_boost_for_bind10.m4
@@ -0,0 +1,112 @@
+dnl @synopsis AX_BOOST_FOR_BIND10
+dnl
+dnl Test for the Boost C++ header files intended to be used within BIND 10
+dnl
+dnl If no path to the installed boost header files is given via the
+dnl --with-boost-include option, the macro searchs under
+dnl /usr/local /usr/pkg /opt /opt/local directories.
+dnl If it cannot detect any workable path for Boost, this macro treats it
+dnl as a fatal error (so it cannot be called if the availability of Boost
+dnl is optional).
+dnl
+dnl This macro also tries to identify some known portability issues, and
+dnl sets corresponding variables so the caller can react to (or ignore,
+dnl depending on other configuration) specific issues appropriately.
+dnl
+dnl This macro calls:
+dnl
+dnl AC_SUBST(BOOST_INCLUDES)
+dnl
+dnl And possibly sets:
+dnl CPPFLAGS_BOOST_THREADCONF should be added to CPPFLAGS by caller
+dnl BOOST_OFFSET_PTR_WOULDFAIL set to "yes" if offset_ptr would cause build
+dnl error; otherwise set to "no"
+dnl BOOST_NUMERIC_CAST_WOULDFAIL set to "yes" if numeric_cast would cause
+dnl build error; otherwise set to "no"
+dnl
+
+AC_DEFUN([AX_BOOST_FOR_BIND10], [
+AC_LANG_SAVE
+AC_LANG([C++])
+
+#
+# Configure Boost header path
+#
+# If explicitly specified, use it.
+AC_ARG_WITH([boost-include],
+ AC_HELP_STRING([--with-boost-include=PATH],
+ [specify exact directory for Boost headers]),
+ [boost_include_path="$withval"])
+# If not specified, try some common paths.
+if test -z "$with_boost_include"; then
+ boostdirs="/usr/local /usr/pkg /opt /opt/local"
+ for d in $boostdirs
+ do
+ if test -f $d/include/boost/shared_ptr.hpp; then
+ boost_include_path=$d/include
+ break
+ fi
+ done
+fi
+
+# Check the path with some specific headers.
+CPPFLAGS_SAVED="$CPPFLAGS"
+if test "${boost_include_path}" ; then
+ BOOST_INCLUDES="-I${boost_include_path}"
+ CPPFLAGS="$CPPFLAGS $BOOST_INCLUDES"
+fi
+AC_CHECK_HEADERS([boost/shared_ptr.hpp boost/foreach.hpp boost/interprocess/sync/interprocess_upgradable_mutex.hpp boost/date_time/posix_time/posix_time_types.hpp boost/bind.hpp boost/function.hpp],,
+ AC_MSG_ERROR([Missing required header files.]))
+
+# Detect whether Boost tries to use threads by default, and, if not,
+# make it sure explicitly. In some systems the automatic detection
+# may depend on preceding header files, and if inconsistency happens
+# it could lead to a critical disruption.
+AC_MSG_CHECKING([whether Boost tries to use threads])
+AC_TRY_COMPILE([
+#include <boost/config.hpp>
+#ifdef BOOST_HAS_THREADS
+#error "boost will use threads"
+#endif],,
+[AC_MSG_RESULT(no)
+ CPPFLAGS_BOOST_THREADCONF="-DBOOST_DISABLE_THREADS=1"],
+[AC_MSG_RESULT(yes)])
+
+# Boost offset_ptr is known to not compile on some platforms, depending on
+# boost version, its local configuration, and compiler. Detect it.
+AC_MSG_CHECKING([Boost offset_ptr compiles])
+AC_TRY_COMPILE([
+#include <boost/interprocess/offset_ptr.hpp>
+],,
+[AC_MSG_RESULT(yes)
+ BOOST_OFFSET_PTR_WOULDFAIL=no],
+[AC_MSG_RESULT(no)
+ BOOST_OFFSET_PTR_WOULDFAIL=yes])
+
+# Detect build failure case known to happen with Boost installed via
+# FreeBSD ports
+if test "X$GXX" = "Xyes"; then
+ CXXFLAGS_SAVED="$CXXFLAGS"
+ CXXFLAGS="$CXXFLAGS -Werror"
+
+ AC_MSG_CHECKING([Boost numeric_cast compiles with -Werror])
+ AC_TRY_COMPILE([
+ #include <boost/numeric/conversion/cast.hpp>
+ ],[
+ return (boost::numeric_cast<short>(0));
+ ],[AC_MSG_RESULT(yes)
+ BOOST_NUMERIC_CAST_WOULDFAIL=no],
+ [AC_MSG_RESULT(no)
+ BOOST_NUMERIC_CAST_WOULDFAIL=yes])
+
+ CXXFLAGS="$CXXFLAGS_SAVED"
+else
+ # This doesn't matter for non-g++
+ BOOST_NUMERIC_CAST_WOULDFAIL=no
+fi
+
+AC_SUBST(BOOST_INCLUDES)
+
+CPPFLAGS="$CPPFLAGS_SAVED"
+AC_LANG_RESTORE
+])dnl AX_BOOST_FOR_BIND10
diff --git a/src/bin/auth/auth_messages.mes b/src/bin/auth/auth_messages.mes
index ac9ffb9..d93da51 100644
--- a/src/bin/auth/auth_messages.mes
+++ b/src/bin/auth/auth_messages.mes
@@ -141,7 +141,7 @@ The specific problem is printed in the log message.
The thread for maintaining data source clients has received a command to
reconfigure, and has now started this process.
-% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed succesfully
+% AUTH_DATASRC_CLIENTS_BUILDER_RECONFIGURE_SUCCESS data source reconfiguration completed successfully
The thread for maintaining data source clients has finished reconfiguring
the data source clients, and is now running with the new configuration.
@@ -169,7 +169,7 @@ probably better to stop and restart it.
% AUTH_DATA_SOURCE data source database file: %1
This is a debug message produced by the authoritative server when it accesses a
-datebase data source, listing the file that is being accessed.
+database data source, listing the file that is being accessed.
% AUTH_DNS_SERVICES_CREATED DNS services created
This is a debug message indicating that the component that will handling
@@ -184,7 +184,7 @@ reason for the failure is given in the message.) The server will drop the
packet.
% AUTH_INVALID_STATISTICS_DATA invalid specification of statistics data specified
-An error was encountered when the authoritiative server specified
+An error was encountered when the authoritative server specified
statistics data which is invalid for the auth specification file.
% AUTH_LOAD_TSIG loading TSIG keys
@@ -208,7 +208,7 @@ requests to b10-ddns) to handle it, but it failed. The authoritative
server returns SERVFAIL to the client on behalf of the separate
process. The error could be configuration mismatch between b10-auth
and the recipient component, or it may be because the requests are
-coming too fast and the receipient process cannot keep up with the
+coming too fast and the recipient process cannot keep up with the
rate, or some system level failure. In either case this means the
BIND 10 system is not working as expected, so the administrator should
look into the cause and address the issue. The log message includes
diff --git a/src/bin/auth/tests/.gitignore b/src/bin/auth/tests/.gitignore
index d6d1ec8..a45eff7 100644
--- a/src/bin/auth/tests/.gitignore
+++ b/src/bin/auth/tests/.gitignore
@@ -1 +1,3 @@
/run_unittests
+/example_base_inc.cc
+/example_nsec3_inc.cc
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 42f202b..d1bde4d 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -7,7 +7,8 @@ AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DAUTH_OBJ_DIR=\"$(abs_top_builddir)/src/bin/auth\"
AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
-AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_top_srcdir)/src/bin/auth/tests/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_DIR=\"$(abs_srcdir)/testdata\"
+AM_CPPFLAGS += -DTEST_OWN_DATA_BUILDDIR=\"$(abs_builddir)/testdata\"
AM_CPPFLAGS += -DTEST_DATA_BUILDDIR=\"$(abs_top_builddir)/src/lib/testutils/testdata\"
AM_CPPFLAGS += -DDSRC_DIR=\"$(abs_top_builddir)/src/lib/datasrc\"
AM_CPPFLAGS += -DPLUGIN_DATA_PATH=\"$(abs_top_builddir)/src/bin/cfgmgr/plugins\"
@@ -50,7 +51,6 @@ run_unittests_SOURCES += config_syntax_unittest.cc
run_unittests_SOURCES += command_unittest.cc
run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
-run_unittests_SOURCES += query_inmemory_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
run_unittests_SOURCES += test_datasrc_clients_mgr.h test_datasrc_clients_mgr.cc
run_unittests_SOURCES += datasrc_clients_builder_unittest.cc
@@ -81,6 +81,39 @@ run_unittests_LDADD += $(top_builddir)/src/lib/util/threads/libb10-threads.la
run_unittests_LDADD += $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+# The following are definitions for auto-generating test data for query
+# tests.
+BUILT_SOURCES = example_base_inc.cc example_nsec3_inc.cc
+BUILT_SOURCES += testdata/example-base.sqlite3
+BUILT_SOURCES += testdata/example-nsec3.sqlite3
+
+EXTRA_DIST = gen-query-testdata.py
+
+CLEANFILES += example_base_inc.cc example_nsec3_inc.cc
+
+example_base_inc.cc: $(srcdir)/testdata/example-base-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-base-inc.zone example_base_inc.cc
+
+example_nsec3_inc.cc: $(srcdir)/testdata/example-nsec3-inc.zone
+ $(PYTHON) $(srcdir)/gen-query-testdata.py \
+ $(srcdir)/testdata/example-nsec3-inc.zone example_nsec3_inc.cc
+
+testdata/example-common-inc.zone: $(srcdir)/testdata/example-common-inc-template.zone
+ $(top_srcdir)/install-sh -c \
+ $(srcdir)/testdata/example-common-inc-template.zone \
+ testdata/example-common-inc.zone
+
+testdata/example-base.sqlite3: testdata/example-base.zone testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-base.sqlite3\"}" \
+ example.com testdata/example-base.zone
+
+testdata/example-nsec3.sqlite3: testdata/example-nsec3.zone testdata/example-common-inc.zone
+ $(SHELL) $(top_builddir)/src/bin/loadzone/run_loadzone.sh \
+ -c "{\"database_file\": \"$(builddir)/testdata/example-nsec3.sqlite3\"}" \
+ example.com testdata/example-nsec3.zone
+
check-local:
B10_FROM_BUILD=${abs_top_builddir} ./run_unittests
diff --git a/src/bin/auth/tests/auth_srv_unittest.cc b/src/bin/auth/tests/auth_srv_unittest.cc
index 7f89fda..826d1b8 100644
--- a/src/bin/auth/tests/auth_srv_unittest.cc
+++ b/src/bin/auth/tests/auth_srv_unittest.cc
@@ -1286,12 +1286,12 @@ TEST_F(AuthSrvTest, queryCounterUnexpected) {
createRequestPacket(request_message, IPPROTO_UDP);
// Modify the message.
- delete io_message;
- endpoint = IOEndpoint::create(IPPROTO_UDP,
- IOAddress(DEFAULT_REMOTE_ADDRESS), 53210);
- io_message = new IOMessage(request_renderer.getData(),
- request_renderer.getLength(),
- getDummyUnknownSocket(), *endpoint);
+ endpoint.reset(IOEndpoint::create(IPPROTO_UDP,
+ IOAddress(DEFAULT_REMOTE_ADDRESS),
+ 53210));
+ io_message.reset(new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ getDummyUnknownSocket(), *endpoint));
EXPECT_FALSE(dnsserv.hasAnswer());
}
@@ -1716,9 +1716,20 @@ void
checkAddrPort(const struct sockaddr& actual_sa,
const string& expected_addr, uint16_t expected_port)
{
+ // ASIO does not set as_len, which is not a problem on most
+ // systems, but it will make getnameinfo() fail on NetBSD 4
+ // So we make a copy and if the field is available, we set it
+ const socklen_t sa_len = getSALength(actual_sa);
+ struct sockaddr_storage ss;
+ memcpy(&ss, &actual_sa, sa_len);
+
+ struct sockaddr* sa = convertSockAddr(&ss);
+#ifdef HAVE_SA_LEN
+ sa->sa_len = sa_len;
+#endif
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
- const int error = getnameinfo(&actual_sa, getSALength(actual_sa), hbuf,
- sizeof(hbuf), sbuf, sizeof(sbuf),
+ const int error = getnameinfo(sa, sa_len, hbuf, sizeof(hbuf),
+ sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (error != 0) {
isc_throw(isc::Unexpected, "getnameinfo failed: " <<
diff --git a/src/bin/auth/tests/gen-query-testdata.py b/src/bin/auth/tests/gen-query-testdata.py
new file mode 100755
index 0000000..a71deb0
--- /dev/null
+++ b/src/bin/auth/tests/gen-query-testdata.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""\
+This is a supplemental script to auto generate test data in the form of
+C++ source code from a DNS zone file.
+
+Usage: python gen-query-testdata.py source_file output-cc-file
+
+The usage doesn't matter much, though, because it's expected to be invoked
+from Makefile, and that would be only use case of this script.
+"""
+
+import sys
+import re
+
+# Markup for variable definition
+re_start_rr = re.compile('^;var=(.*)')
+
+# Skip lines starting with ';' (comments) or empty lines. re_start_rr
+# will also match this expression, so it should be checked first.
+re_skip = re.compile('(^;)|(^\s*$)')
+
+def parse_input(input_file):
+ '''Build an internal list of RR data from the input source file.
+
+ It generates a list of (variable_name, list of RR) tuples, where
+ variable_name is the expected C++ variable name for the subsequent RRs
+ if they are expected to be named. It can be an empty string if the RRs
+ are only expected to appear in the zone file.
+ The second element of the tuple is a list of strings, each of which
+ represents a single RR, e.g., "example.com 3600 IN A 192.0.2.1".
+
+ '''
+ result = []
+ rrs = None
+ with open(input_file) as f:
+ for line in f:
+ m = re_start_rr.match(line)
+ if m:
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+ rrs = []
+ rr_varname = m.group(1)
+ elif re_skip.match(line):
+ continue
+ else:
+ rrs.append(line.rstrip('\n'))
+
+ # if needed, store the last RRs (they are not followed by 'var=' mark)
+ if rrs is not None:
+ result.append((rr_varname, rrs))
+
+ return result
+
+def generate_variables(out_file, rrsets_data):
+ '''Generate a C++ source file containing a C-string variables for RRs.
+
+ This produces a definition of C-string for each RRset that is expected
+ to be named as follows:
+ const char* const var_name =
+ "example.com. 3600 IN A 192.0.2.1\n"
+ "example.com. 3600 IN A 192.0.2.2\n";
+
+ Escape character '\' in the string will be further escaped so it will
+ compile.
+
+ '''
+ with open(out_file, 'w') as out:
+ for (var_name, rrs) in rrsets_data:
+ if len(var_name) > 0:
+ out.write('const char* const ' + var_name + ' =\n')
+ # Combine all RRs, escaping '\' as a C-string
+ out.write('\n'.join([' \"%s\\n\"' %
+ (rr.replace('\\', '\\\\'))
+ for rr in rrs]))
+ out.write(';\n')
+
+if __name__ == "__main__":
+ if len(sys.argv) < 3:
+ sys.stderr.write('gen-query-testdata.py require 2 args\n')
+ sys.exit(1)
+ rrsets_data = parse_input(sys.argv[1])
+ generate_variables(sys.argv[2], rrsets_data)
+
diff --git a/src/bin/auth/tests/query_inmemory_unittest.cc b/src/bin/auth/tests/query_inmemory_unittest.cc
deleted file mode 100644
index f587ba2..0000000
--- a/src/bin/auth/tests/query_inmemory_unittest.cc
+++ /dev/null
@@ -1,123 +0,0 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include <dns/name.h>
-#include <dns/message.h>
-#include <dns/rcode.h>
-#include <dns/opcode.h>
-
-#include <cc/data.h>
-
-#include <datasrc/client_list.h>
-
-#include <auth/query.h>
-
-#include <testutils/dnsmessage_test.h>
-
-#include <gtest/gtest.h>
-
-#include <string>
-
-using namespace isc::dns;
-using namespace isc::auth;
-using namespace isc::testutils;
-using isc::datasrc::ConfigurableClientList;
-using std::string;
-
-namespace {
-
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// This is not inside the zone, this is created at runtime
-const char* const synthetized_cname_txt =
- "www.dname.example.com. 3600 IN CNAME "
- "www.somethinglong.dnametarget.example.com.\n";
-
-// This is a subset of QueryTest using (subset of) the same test data, but
-// with the production in-memory data source. Both tests should be eventually
-// unified to avoid duplicates.
-class InMemoryQueryTest : public ::testing::Test {
-protected:
- InMemoryQueryTest() : list(RRClass::IN()), response(Message::RENDER) {
- response.setRcode(Rcode::NOERROR());
- response.setOpcode(Opcode::QUERY());
- list.configure(isc::data::Element::fromJSON(
- "[{\"type\": \"MasterFiles\","
- " \"cache-enable\": true, "
- " \"params\": {\"example.com\": \"" +
- string(TEST_OWN_DATA_DIR "/example.zone") +
- "\"}}]"), true);
- }
-
- ConfigurableClientList list;
- Message response;
- Query query;
-};
-
-// A wrapper to check resulting response message commonly used in
-// tests below.
-// check_origin needs to be specified only when the authority section has
-// an SOA RR. The interface is not generic enough but should be okay
-// for our test cases in practice.
-void
-responseCheck(Message& response, const isc::dns::Rcode& rcode,
- unsigned int flags, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount,
- const char* const expected_answer,
- const char* const expected_authority,
- const char* const expected_additional,
- const Name& check_origin = Name::ROOT_NAME())
-{
- // In our test cases QID, Opcode, and QDCOUNT should be constant, so
- // we don't bother the test cases specifying these values.
- headerCheck(response, response.getQid(), rcode, Opcode::QUERY().getCode(),
- flags, 0, ancount, nscount, arcount);
- if (expected_answer != NULL) {
- rrsetsCheck(expected_answer,
- response.beginSection(Message::SECTION_ANSWER),
- response.endSection(Message::SECTION_ANSWER),
- check_origin);
- }
- if (expected_authority != NULL) {
- rrsetsCheck(expected_authority,
- response.beginSection(Message::SECTION_AUTHORITY),
- response.endSection(Message::SECTION_AUTHORITY),
- check_origin);
- }
- if (expected_additional != NULL) {
- rrsetsCheck(expected_additional,
- response.beginSection(Message::SECTION_ADDITIONAL),
- response.endSection(Message::SECTION_ADDITIONAL));
- }
-}
-
-/*
- * Test a query under a domain with DNAME. We should get a synthetized CNAME
- * as well as the DNAME.
- *
- * TODO: Once we have CNAME chaining, check it works with synthetized CNAMEs
- * as well. This includes tests pointing inside the zone, outside the zone,
- * pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
- */
-TEST_F(InMemoryQueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
- response);
-
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
- (string(dname_txt) + synthetized_cname_txt).c_str(),
- NULL, NULL);
-}
-}
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 84b7f8a..a22d2d7 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -12,10 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <map>
-#include <sstream>
-#include <vector>
-
#include <boost/bind.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/static_assert.hpp>
@@ -24,9 +20,12 @@
#include <dns/masterload.h>
#include <dns/message.h>
+#include <dns/master_loader.h>
#include <dns/name.h>
+#include <dns/nsec3hash.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
+#include <dns/rrcollator.h>
#include <dns/rrttl.h>
#include <dns/rrtype.h>
#include <dns/rdataclass.h>
@@ -40,6 +39,12 @@
#include <gtest/gtest.h>
+#include <cstdlib>
+#include <fstream>
+#include <map>
+#include <sstream>
+#include <vector>
+
using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
@@ -49,7 +54,7 @@ using namespace isc::testutils;
namespace {
-// Simple wrapper for a sincle data source client.
+// Simple wrapper for a single data source client.
// The list simply delegates all the answers to the single
// client.
class SingletonList : public ClientList {
@@ -79,150 +84,21 @@ private:
DataSourceClient& client_;
};
+// These are commonly used data (auto-generated). There are some exceptional
+// data that are only used in a limited scenario, which are defined separately
+// below.
+#include <auth/tests/example_base_inc.cc>
+#include <auth/tests/example_nsec3_inc.cc>
-// This is the content of the mock zone (see below).
-// It's a sequence of textual RRs that is supposed to be parsed by
-// dns::masterLoad(). Some of the RRs are also used as the expected
-// data in specific tests, in which case they are referenced via specific
-// local variables (such as soa_txt).
-//
-// For readability consistency, all strings are placed in a separate line,
-// even if they are very short and can reasonably fit in a single line with
-// the corresponding variable. For example, we write
-// const char* const foo_txt =
-// "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-// instead of
-// const char* const foo_txt = "foo.example.com. 3600 IN AAAA 2001:db8::1\n";
-const char* const soa_txt =
- "example.com. 3600 IN SOA . . 0 0 0 0 0\n";
-const char* const zone_ns_txt =
- "example.com. 3600 IN NS glue.delegation.example.com.\n"
- "example.com. 3600 IN NS noglue.example.com.\n"
- "example.com. 3600 IN NS example.net.\n";
+// This is used only in one pathological test case.
const char* const zone_ds_txt =
"example.com. 3600 IN DS 57855 5 1 "
"B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const ns_addrs_txt =
- "glue.delegation.example.com. 3600 IN A 192.0.2.153\n"
- "glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n"
- "noglue.example.com. 3600 IN A 192.0.2.53\n";
-const char* const delegation_txt =
- "delegation.example.com. 3600 IN NS glue.delegation.example.com.\n"
- "delegation.example.com. 3600 IN NS noglue.example.com.\n"
- "delegation.example.com. 3600 IN NS cname.example.com.\n"
- "delegation.example.com. 3600 IN NS example.org.\n";
-// Borrowed from the RFC4035
-const char* const delegation_ds_txt =
- "delegation.example.com. 3600 IN DS 57855 5 1 "
- "B6DCD485719ADCA18E5F3D48A2331627FDD3 636B\n";
-const char* const mx_txt =
- "mx.example.com. 3600 IN MX 10 www.example.com.\n"
- "mx.example.com. 3600 IN MX 20 mailer.example.org.\n"
- "mx.example.com. 3600 IN MX 30 mx.delegation.example.com.\n";
-const char* const www_a_txt =
- "www.example.com. 3600 IN A 192.0.2.80\n";
-const char* const cname_txt =
- "cname.example.com. 3600 IN CNAME www.example.com.\n";
-const char* const cname_nxdom_txt =
- "cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.\n";
-// CNAME Leading out of zone
-const char* const cname_out_txt =
- "cnameout.example.com. 3600 IN CNAME www.example.org.\n";
-// The DNAME to do tests against
-const char* const dname_txt =
- "dname.example.com. 3600 IN DNAME "
- "somethinglong.dnametarget.example.com.\n";
-// Some data at the dname node (allowed by RFC 2672)
-const char* const dname_a_txt =
- "dname.example.com. 3600 IN A 192.0.2.5\n";
+
// This is not inside the zone, this is created at runtime
const char* const synthetized_cname_txt =
"www.dname.example.com. 3600 IN CNAME "
"www.somethinglong.dnametarget.example.com.\n";
-// The rest of data won't be referenced from the test cases.
-const char* const other_zone_rrs =
- "cnamemailer.example.com. 3600 IN CNAME www.example.com.\n"
- "cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.\n"
- "mx.delegation.example.com. 3600 IN A 192.0.2.100\n";
-// Wildcards
-const char* const wild_txt =
- "*.wild.example.com. 3600 IN A 192.0.2.7\n";
-const char* const nsec_wild_txt =
- "*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG\n";
-const char* const cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN CNAME www.example.org.\n";
-const char* const nsec_cnamewild_txt =
- "*.cnamewild.example.com. 3600 IN NSEC "
- "delegation.example.com. CNAME NSEC RRSIG\n";
-// Wildcard_nxrrset
-const char* const wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN A 192.0.2.9\n";
-const char* const nsec_wild_txt_nxrrset =
- "*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG\n";
-const char* const wild_txt_next =
- "www.uwild.example.com. 3600 IN A 192.0.2.11\n";
-const char* const nsec_wild_txt_next =
- "www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG\n";
-// Wildcard empty
-const char* const empty_txt =
- "b.*.t.example.com. 3600 IN A 192.0.2.13\n";
-const char* const nsec_empty_txt =
- "b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG\n";
-const char* const empty_prev_txt =
- "t.example.com. 3600 IN A 192.0.2.15\n";
-const char* const nsec_empty_prev_txt =
- "t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG\n";
-// Used in NXDOMAIN proof test. We are going to test some unusual case where
-// the best possible wildcard is below the "next domain" of the NSEC RR that
-// proves the NXDOMAIN, i.e.,
-// mx.example.com. (exist)
-// (.no.example.com. (qname, NXDOMAIN)
-// ).no.example.com. (exist)
-// *.no.example.com. (best possible wildcard, not exist)
-const char* const no_txt =
- ").no.example.com. 3600 IN AAAA 2001:db8::53\n";
-// NSEC records.
-const char* const nsec_apex_txt =
- "example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG\n";
-const char* const nsec_mx_txt =
- "mx.example.com. 3600 IN NSEC ).no.example.com. MX NSEC RRSIG\n";
-const char* const nsec_no_txt =
- ").no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG\n";
-// We'll also test the case where a single NSEC proves both NXDOMAIN and the
-// non existence of wildcard. The following records will be used for that
-// test.
-// ).no.example.com. (exist, whose NSEC proves everything)
-// *.no.example.com. (best possible wildcard, not exist)
-// nx.no.example.com. (NXDOMAIN)
-// nz.no.example.com. (exist)
-const char* const nz_txt =
- "nz.no.example.com. 3600 IN AAAA 2001:db8::5300\n";
-const char* const nsec_nz_txt =
- "nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG\n";
-const char* const nsec_nxdomain_txt =
- "noglue.example.com. 3600 IN NSEC nonsec.example.com. A\n";
-
-// NSEC for the normal NXRRSET case
-const char* const nsec_www_txt =
- "www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG\n";
-
-// Authoritative data without NSEC
-const char* const nonsec_a_txt =
- "nonsec.example.com. 3600 IN A 192.0.2.0\n";
-
-// NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-const string nsec3_apex_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG\n";
-const string nsec3_apex_rrsig_txt =
- "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
-const string nsec3_www_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 "
- "aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-const string nsec3_www_rrsig_txt =
- "q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 "
- "3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE";
// NSEC3 for wild.example.com (used in wildcard tests, will be added on
// demand not to confuse other tests)
@@ -246,42 +122,13 @@ const char* const nsec3_uwild_txt =
"t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG\n";
-// (Secure) delegation data; Delegation with DS record
-const char* const signed_delegation_txt =
- "signed-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const signed_delegation_ds_txt =
- "signed-delegation.example.com. 3600 IN DS 12345 8 2 "
- "764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA\n";
-
// (Secure) delegation data; Delegation without DS record (and both NSEC
// and NSEC3 denying its existence)
-const char* const unsigned_delegation_txt =
- "unsigned-delegation.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_nsec_txt =
- "unsigned-delegation.example.com. 3600 IN NSEC "
- "unsigned-delegation-optout.example.com. NS RRSIG NSEC\n";
// This one will be added on demand
const char* const unsigned_delegation_nsec3_txt =
"q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 "
"aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG\n";
-// Delegation without DS record, and no direct matching NSEC3 record
-const char* const unsigned_delegation_optout_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.\n";
-const char* const unsigned_delegation_optout_nsec_txt =
- "unsigned-delegation-optout.example.com. 3600 IN NSEC "
- "*.uwild.example.com. NS RRSIG NSEC\n";
-
-// (Secure) delegation data; Delegation where the DS lookup will raise an
-// exception.
-const char* const bad_delegation_txt =
- "bad-delegation.example.com. 3600 IN NS ns.example.net.\n";
-
-// Delegation from an unsigned parent. There's no DS, and there's no NSEC
-// or NSEC3 that proves it.
-const char* const nosec_delegation_txt =
- "nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.\n";
-
// A helper function that generates a textual representation of RRSIG RDATA
// for the given covered type. The resulting RRSIG may not necessarily make
// sense in terms of the DNSSEC protocol, but for our testing purposes it's
@@ -314,58 +161,14 @@ textToRRset(const string& text_rrset, const Name& origin = Name::ROOT_NAME()) {
return (rrset);
}
-// This is a mock Zone Finder class for testing.
-// It is a derived class of ZoneFinder for the convenient of tests.
-// Its find() method emulates the common behavior of protocol compliant
-// ZoneFinder classes, but simplifies some minor cases and also supports broken
-// behavior.
-// For simplicity, most names are assumed to be "in zone"; delegations
-// to child zones are identified by the existence of non origin NS records.
-// Another special name is "dname.example.com". Query names under this name
-// will result in DNAME.
-// This mock zone doesn't handle empty non terminal nodes (if we need to test
-// such cases find() should have specialized code for it).
-class MockZoneFinder : public ZoneFinder {
+// Setup for faked NSEC3 hash used throughout this test.
+class TestNSEC3Hash : public NSEC3Hash {
+private:
+ typedef map<Name, string> NSEC3HashMap;
+ typedef NSEC3HashMap::value_type NSEC3HashPair;
+ NSEC3HashMap hash_map_;
public:
- MockZoneFinder() :
- origin_(Name("example.com")),
- bad_signed_delegation_name_("bad-delegation.example.com"),
- dname_name_("dname.example.com"),
- has_SOA_(true),
- has_apex_NS_(true),
- rrclass_(RRClass::IN()),
- include_rrsig_anyway_(false),
- use_nsec3_(false),
- nsec_name_(origin_),
- nsec3_fake_(NULL),
- nsec3_name_(NULL)
- {
- stringstream zone_stream;
- zone_stream << soa_txt << zone_ns_txt << ns_addrs_txt <<
- delegation_txt << delegation_ds_txt << mx_txt << www_a_txt <<
- cname_txt << cname_nxdom_txt << cname_out_txt << dname_txt <<
- dname_a_txt << other_zone_rrs << no_txt << nz_txt <<
- nsec_apex_txt << nsec_mx_txt << nsec_no_txt << nsec_nz_txt <<
- nsec_nxdomain_txt << nsec_www_txt << nonsec_a_txt <<
- wild_txt << nsec_wild_txt << cnamewild_txt << nsec_cnamewild_txt <<
- wild_txt_nxrrset << nsec_wild_txt_nxrrset << wild_txt_next <<
- nsec_wild_txt_next << empty_txt << nsec_empty_txt <<
- empty_prev_txt << nsec_empty_prev_txt <<
- nsec3_apex_txt << nsec3_www_txt <<
- signed_delegation_txt << signed_delegation_ds_txt <<
- unsigned_delegation_txt << unsigned_delegation_nsec_txt <<
- unsigned_delegation_optout_txt <<
- unsigned_delegation_optout_nsec_txt <<
- bad_delegation_txt << nosec_delegation_txt;
-
- masterLoad(zone_stream, origin_, rrclass_,
- boost::bind(&MockZoneFinder::loadRRset, this, _1));
-
- empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
- RRClass::IN(),
- RRType::NSEC(),
- RRTTL(3600)));
-
+ TestNSEC3Hash() {
// (Faked) NSEC3 hash map. For convenience we use hardcoded built-in
// map instead of calculating and using actual hash.
// The used hash values are borrowed from RFC5155 examples (they are
@@ -411,6 +214,79 @@ public:
hash_map_[Name("www1.uwild.example.com")] =
"q04jkcevqvmu85r014c7dkba38o0ji6r"; // a bit larger than H(www)
}
+ virtual string calculate(const Name& name) const {
+ const NSEC3HashMap::const_iterator found = hash_map_.find(name);
+ if (found != hash_map_.end()) {
+ return (found->second);
+ }
+ isc_throw(isc::Unexpected, "unexpected name for NSEC3 test: "
+ << name);
+ }
+ virtual bool match(const rdata::generic::NSEC3PARAM&) const {
+ return (true);
+ }
+ virtual bool match(const rdata::generic::NSEC3&) const {
+ return (true);
+ }
+};
+
+class TestNSEC3HashCreator : public isc::dns::NSEC3HashCreator {
+public:
+ TestNSEC3HashCreator() {}
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3PARAM&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(const isc::dns::rdata::generic::NSEC3&) const {
+ return (new TestNSEC3Hash);
+ }
+
+ virtual isc::dns::NSEC3Hash*
+ create(uint8_t, uint16_t, const uint8_t*, size_t) const {
+ return (new TestNSEC3Hash);
+ }
+};
+
+// This is a mock Zone Finder class for testing.
+// It is a derived class of ZoneFinder for the convenient of tests.
+// Its find() method emulates the common behavior of protocol compliant
+// ZoneFinder classes, but simplifies some minor cases and also supports broken
+// behavior.
+// For simplicity, most names are assumed to be "in zone"; delegations
+// to child zones are identified by the existence of non origin NS records.
+// Another special name is "dname.example.com". Query names under this name
+// will result in DNAME.
+// This mock zone doesn't handle empty non terminal nodes (if we need to test
+// such cases find() should have specialized code for it).
+class MockZoneFinder : public ZoneFinder {
+public:
+ MockZoneFinder() :
+ origin_(Name("example.com")),
+ bad_signed_delegation_name_("bad-delegation.example.com"),
+ dname_name_("dname.example.com"),
+ has_SOA_(true),
+ has_apex_NS_(true),
+ rrclass_(RRClass::IN()),
+ include_rrsig_anyway_(false),
+ use_nsec3_(false),
+ nsec_name_(origin_),
+ nsec3_fake_(NULL),
+ nsec3_name_(NULL)
+ {
+ RRCollator collator(boost::bind(&MockZoneFinder::loadRRset, this, _1));
+ MasterLoader loader(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone",
+ origin_, rrclass_,
+ MasterLoaderCallbacks::getNullCallbacks(),
+ collator.getCallback());
+ loader.load();
+
+ empty_nsec_rrset_ = ConstRRsetPtr(new RRset(Name::ROOT_NAME(),
+ RRClass::IN(),
+ RRType::NSEC(),
+ RRTTL(3600)));
+ }
virtual isc::dns::Name getOrigin() const { return (origin_); }
virtual isc::dns::RRClass getClass() const { return (rrclass_); }
virtual ZoneFinderContextPtr find(const isc::dns::Name& name,
@@ -513,6 +389,19 @@ private:
};
void loadRRset(RRsetPtr rrset) {
+ // For simplicity we dynamically generate RRSIGs and add them below.
+ // The RRSIG RDATA should be consistent with that defined in the
+ // zone file.
+ if (rrset->getType() == RRType::RRSIG()) {
+ return;
+ }
+
+ // NSEC3PARAM is not used in the mock data source (and it would confuse
+ // non-NSEC3 test cases).
+ if (rrset->getType() == RRType::NSEC3PARAM()) {
+ return;
+ }
+
if (rrset->getType() == RRType::NSEC3()) {
// NSEC3 should go to the dedicated table
nsec3_domains_[rrset->getName()][rrset->getType()] = rrset;
@@ -565,9 +454,7 @@ private:
// Enabled when not NULL
const FindNSEC3Result* nsec3_fake_;
const Name* nsec3_name_;
-public:
- // Public, to allow tests looking up the right names for something
- map<Name, string> hash_map_;
+ TestNSEC3Hash nsec3_hash_;
};
// A helper function that generates a new RRset based on "wild_rrset",
@@ -627,11 +514,7 @@ MockZoneFinder::findNSEC3(const Name& name, bool recursive) {
// For brevity, we assume several things below: maps should have an
// expected entry when operator[] is used; maps are not empty.
for (int i = 0; i < labels; ++i) {
- const string hlabel = hash_map_[name.split(i, labels - i)];
- if (hlabel.empty()) {
- isc_throw(isc::Unexpected, "findNSEC3() hash failure for " <<
- name.split(i, labels - i));
- }
+ const string hlabel = nsec3_hash_.calculate(name.split(i, labels - i));
const Name hname = Name(hlabel + ".example.com");
// We don't use const_iterator so that we can use operator[] below
Domains::iterator found_domain = nsec3_domains_.lower_bound(hname);
@@ -893,10 +776,55 @@ MockZoneFinder::find(const Name& name, const RRType& type,
return (createContext(options,NXDOMAIN, RRsetPtr()));
}
-class QueryTest : public ::testing::Test {
+enum DataSrcType {
+ MOCK,
+ INMEMORY,
+ SQLITE3
+};
+
+boost::shared_ptr<ClientList>
+createDataSrcClientList(DataSrcType type, DataSourceClient& client) {
+ boost::shared_ptr<ConfigurableClientList> list;
+ switch (type) {
+ case MOCK:
+ return (boost::shared_ptr<ClientList>(new SingletonList(client)));
+ case INMEMORY:
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR "/example.zone") +
+ "\"}}]"), true);
+ return (list);
+ case SQLITE3:
+ // The copy should succeed; if it failed we should notice it in
+ // test cases. However, we check the return value to avoid problems
+ // in some glibcs where "system()" is annotated with the "warn unused
+ // result" attribute.
+ EXPECT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied"));
+ list.reset(new ConfigurableClientList(RRClass::IN()));
+ list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-base.sqlite3.copied") +
+ "\"}}]"), true);
+ return (list);
+ default:
+ isc_throw(isc::Unexpected,
+ "Unexpected data source type, should be a bug in test code");
+ }
+}
+
+class QueryTest : public ::testing::TestWithParam<DataSrcType> {
protected:
QueryTest() :
- list(memory_client),
qname(Name("www.example.com")), qclass(RRClass::IN()),
qtype(RRType::A()), response(Message::RENDER),
qid(response.getQid()), query_code(Opcode::QUERY().getCode()),
@@ -906,22 +834,166 @@ protected:
"glue.delegation.example.com. 3600 IN RRSIG " +
getCommonRRSIGText("AAAA") + "\n" +
"noglue.example.com. 3600 IN RRSIG " +
- getCommonRRSIGText("A"))
+ getCommonRRSIGText("A")),
+ base_zone_file(TEST_OWN_DATA_BUILDDIR "/example-base.zone"),
+ nsec3_zone_file(TEST_OWN_DATA_BUILDDIR "/example-nsec3.zone"),
+ common_zone_file(TEST_OWN_DATA_BUILDDIR "/example-common-inc.zone"),
+ rrsets_added_(false)
{
+ // Set up the faked hash calculator.
+ setNSEC3HashCreator(&nsec3hash_creator_);
+
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
// create and add a matching zone.
mock_finder = new MockZoneFinder();
memory_client.addZone(ZoneFinderPtr(mock_finder));
}
+
+ virtual void SetUp() {
+ // clear the commonly included zone file.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+
+ // We create data source clients here, not in the constructor, so this
+ // doesn't happen for derived test class. This also ensures the
+ // data source clients are configured after setting NSEC3 hash in case
+ // there's dependency.
+ list_ = createDataSrcClientList(GetParam(), memory_client);
+ }
+
+ virtual void TearDown() {
+ // make sure to clear the commonly included zone file to prevent
+ // any remaining contents from affecting the next test.
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_DIR
+ "/example-common-inc-template.zone "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-common-inc.zone"));
+ }
+
+ virtual ~QueryTest() {
+ // Make sure we reset the hash creator to the default
+ setNSEC3HashCreator(NULL);
+ }
+
+ void enableNSEC3(const vector<string>& rrsets_to_add) {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ switch (GetParam()) {
+ case MOCK:
+ mock_finder->setNSEC3Flag(true);
+ addRRsets(rrsets_to_add, *list_, "");
+ break;
+ case INMEMORY:
+ addRRsets(rrsets_to_add, *list_, nsec3_zone_file);
+ break;
+ case SQLITE3:
+ ASSERT_EQ(0, std::system(INSTALL_PROG " -c " TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3 "
+ TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied"));
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"sqlite3\","
+ " \"cache-enable\": false, "
+ " \"cache-zones\": [], "
+ " \"params\": {\"database_file\": \"" +
+ string(TEST_OWN_DATA_BUILDDIR
+ "/example-nsec3.sqlite3.copied") +
+ "\"}}]"), true);
+ addRRsets(rrsets_to_add, *new_list, "");
+ list_ = new_list;
+ break;
+ }
+ }
+
+ // A helper to add some RRsets to the test zone in the middle of a test
+ // case. The detailed behavior is quite different depending on the
+ // data source type, and not all parameters are used in all cases.
+ //
+ // Note: due to limitation of its implementation, this method doesn't
+ // work correctly for in-memory if called more than once. This condition
+ // is explicitly checked so any accidental violation would be noted as a
+ // test failure.
+ void addRRsets(const vector<string>& rrsets_to_add, ClientList& list,
+ const string& zone_file)
+ {
+ boost::shared_ptr<ConfigurableClientList> new_list;
+ ofstream ofs;
+
+ switch (GetParam()) {
+ case MOCK:
+ // directly add them to the mock data source; ignore the passed
+ // list.
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ mock_finder->addRecord(*it);
+ }
+ break;
+ case INMEMORY:
+ ASSERT_FALSE(rrsets_added_);
+ rrsets_added_ = true;
+
+ // dump the RRsets to be added to the placeholder of commonly
+ // included zone file (removing any existing contents) and do
+ // full reconfiguration.
+ ofs.open(common_zone_file.c_str(), ios_base::trunc);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ofs << *it << "\n";
+ ofs << createRRSIG(textToRRset(*it))->toText() << "\n";
+ }
+ ofs.close();
+
+ new_list.reset(new ConfigurableClientList(RRClass::IN()));
+ new_list->configure(isc::data::Element::fromJSON(
+ "[{\"type\": \"MasterFiles\","
+ " \"cache-enable\": true, "
+ " \"params\": {\"example.com\": \"" +
+ zone_file + "\"}}]"), true);
+ list_ = new_list;
+ break;
+ case SQLITE3:
+ const Name origin("example.com");
+ ZoneUpdaterPtr updater =
+ list.find(origin, true, false).dsrc_client_->
+ getUpdater(origin, false);
+ for (vector<string>::const_iterator it = rrsets_to_add.begin();
+ it != rrsets_to_add.end();
+ ++it) {
+ ConstRRsetPtr rrset = textToRRset(*it);
+ updater->addRRset(*rrset);
+ updater->addRRset(*createRRSIG(rrset));
+ }
+ updater->commit();
+ break;
+ }
+ }
+
+private:
+ // A helper for enableNSEC3, creating an RRSIG RRset for the corresponding
+ // non-sig RRset, using the commonly used parameters.
+ static ConstRRsetPtr createRRSIG(ConstRRsetPtr rrset) {
+ RRsetPtr sig_rrset(new RRset(rrset->getName(), rrset->getClass(),
+ RRType::RRSIG(), rrset->getTTL()));
+ sig_rrset->addRdata(generic::RRSIG(
+ getCommonRRSIGText(rrset->getType().
+ toText())));
+ return (sig_rrset);
+ }
+
+protected:
MockZoneFinder* mock_finder;
// We use InMemoryClient here. We could have some kind of mock client
// here, but historically, the Query supported only InMemoryClient
// (originally named MemoryDataSrc) and was tested with it, so we keep
// it like this for now.
InMemoryClient memory_client;
- // A wrapper client list to wrap the single data source.
- SingletonList list;
+
+ boost::shared_ptr<ClientList> list_;
const Name qname;
const RRClass qclass;
const RRType qtype;
@@ -930,6 +1002,37 @@ protected:
const uint16_t query_code;
const string ns_addrs_and_sig_txt; // convenient shortcut
Query query;
+ TestNSEC3Hash nsec3_hash_;
+ vector<string> rrsets_to_add_;
+ const string base_zone_file;
+private:
+ const string nsec3_zone_file;
+ const string common_zone_file;
+ const TestNSEC3HashCreator nsec3hash_creator_;
+ bool rrsets_added_;
+};
+
+// We test the in-memory and SQLite3 data source implementations. SQLite3
+// will require a loadable module, which doesn't work with static link for
+// all platforms.
+INSTANTIATE_TEST_CASE_P(, QueryTest,
+ ::testing::Values(MOCK, INMEMORY
+#ifndef USE_STATIC_LINK
+ , SQLITE3
+#endif
+ ));
+
+// This inherit the QueryTest cases except for the parameterized setup;
+// it's intended to be used selected test cases that only work for mock
+// data sources either because of some limitation or because of the type of
+// tests (relying on a "broken" data source behavior that can't happen with
+// production data source implementations).
+class QueryTestForMockOnly : public QueryTest {
+protected:
+ // Override SetUp() to avoid parameterized setup
+ virtual void SetUp() {
+ list_ = createDataSrcClientList(MOCK, memory_client);
+ }
};
// A wrapper to check resulting response message commonly used in
@@ -969,7 +1072,7 @@ responseCheck(Message& response, const isc::dns::Rcode& rcode,
}
}
-TEST_F(QueryTest, noZone) {
+TEST_P(QueryTest, noZone) {
// There's no zone in the memory datasource. So the response should have
// REFUSED.
InMemoryClient empty_memory_client;
@@ -979,15 +1082,15 @@ TEST_F(QueryTest, noZone) {
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
-TEST_F(QueryTest, exactMatch) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatch) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchMultipleQueries) {
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+TEST_P(QueryTest, exactMatchMultipleQueries) {
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
@@ -996,27 +1099,26 @@ TEST_F(QueryTest, exactMatchMultipleQueries) {
response.clear(isc::dns::Message::RENDER);
response.setRcode(Rcode::NOERROR());
response.setOpcode(Opcode::QUERY());
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
SCOPED_TRACE("Second query");
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, exactMatchIgnoreSIG) {
+TEST_P(QueryTest, exactMatchIgnoreSIG) {
// Check that we do not include the RRSIG when not requested even when
// we receive it from the data source.
mock_finder->setIncludeRRSIGAnyway(true);
- EXPECT_NO_THROW(query.process(list, qname, qtype, response));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
www_a_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, dnssecPositive) {
+TEST_P(QueryTest, dnssecPositive) {
// Just like exactMatch, but the signatures should be included as well
- EXPECT_NO_THROW(query.process(list, qname, qtype, response,
- true));
+ EXPECT_NO_THROW(query.process(*list_, qname, qtype, response, true));
// find match rrset
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 4, 6,
(www_a_txt + std::string("www.example.com. 3600 IN RRSIG "
@@ -1031,10 +1133,10 @@ TEST_F(QueryTest, dnssecPositive) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, exactAddrMatch) {
+TEST_P(QueryTest, exactAddrMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("noglue.example.com"),
qtype, response));
@@ -1044,10 +1146,10 @@ TEST_F(QueryTest, exactAddrMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexNSMatch) {
+TEST_P(QueryTest, apexNSMatch) {
// find match rrset, omit authority data which has already been provided
// in the answer section from the authority section.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::NS(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 0, 3,
@@ -1055,10 +1157,16 @@ TEST_F(QueryTest, apexNSMatch) {
}
// test type any query logic
-TEST_F(QueryTest, exactAnyMatch) {
+TEST_P(QueryTest, exactAnyMatch) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("noglue.example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("noglue.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 3, 2,
@@ -1069,10 +1177,10 @@ TEST_F(QueryTest, exactAnyMatch) {
"glue.delegation.example.com. 3600 IN AAAA 2001:db8::53\n");
}
-TEST_F(QueryTest, apexAnyMatch) {
+TEST_P(QueryTest, apexAnyMatch) {
// find match rrset, omit additional data which has already been provided
// in the answer section from the additional.
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 5, 0, 3,
(string(soa_txt) + string(zone_ns_txt) +
@@ -1080,23 +1188,23 @@ TEST_F(QueryTest, apexAnyMatch) {
NULL, ns_addrs_txt, mock_finder->getOrigin());
}
-TEST_F(QueryTest, mxANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("mx.example.com"),
+TEST_P(QueryTest, mxANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("mx.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 3, 4,
(string(mx_txt) + string(nsec_mx_txt)).c_str(), zone_ns_txt,
(string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
-TEST_F(QueryTest, glueANYMatch) {
- EXPECT_NO_THROW(query.process(list, Name("delegation.example.com"),
+TEST_P(QueryTest, glueANYMatch) {
+ EXPECT_NO_THROW(query.process(*list_, Name("delegation.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NOERROR(), 0, 0, 4, 3,
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, nodomainANY) {
- EXPECT_NO_THROW(query.process(list, Name("nxdomain.example.com"),
+TEST_P(QueryTest, nodomainANY) {
+ EXPECT_NO_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::ANY(), response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
@@ -1105,17 +1213,20 @@ TEST_F(QueryTest, nodomainANY) {
// This tests that when we need to look up Zone's apex NS records for
// authoritative answer, and there is no apex NS records. It should
// throw in that case.
-TEST_F(QueryTest, noApexNS) {
+//
+// This only works with mock data source (for production datasrc the
+// post-load would reject such a zone)
+TEST_F(QueryTestForMockOnly, noApexNS) {
// Disable apex NS record
mock_finder->setApexNSFlag(false);
- EXPECT_THROW(query.process(list, Name("noglue.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("noglue.example.com"), qtype,
response), Query::NoApexNS);
// We don't look into the response, as it threw
}
-TEST_F(QueryTest, delegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, delegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
qtype, response));
@@ -1123,19 +1234,19 @@ TEST_F(QueryTest, delegation) {
NULL, delegation_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, delegationWithDNSSEC) {
+TEST_P(QueryTest, delegationWithDNSSEC) {
// Similar to the previous one, but with requesting DNSSEC.
// In this case the parent zone would behave as unsigned, so the result
// should be just like non DNSSEC delegation.
- query.process(list, Name("www.nosec-delegation.example.com"),
+ query.process(*list_, Name("www.nosec-delegation.example.com"),
qtype, response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 1, 0,
NULL, nosec_delegation_txt, NULL);
}
-TEST_F(QueryTest, secureDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.signed-delegation.example.com"),
qtype, response, true));
@@ -1149,8 +1260,8 @@ TEST_F(QueryTest, secureDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegation) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, secureUnsignedDelegation) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true));
@@ -1164,33 +1275,33 @@ TEST_F(QueryTest, secureUnsignedDelegation) {
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3) {
// Similar to the previous case, but the zone is signed with NSEC3,
// and this delegation is NOT an optout.
- const Name insecurechild_name("unsigned-delegation.example.com");
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
// The response should contain the NS and matching NSEC3 with its RRSIG
+ const Name insecurechild_name("unsigned-delegation.example.com");
responseCheck(response, Rcode::NOERROR(), 0, 0, 3, 0,
NULL,
(string(unsigned_delegation_txt) +
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[insecurechild_name] +
+ nsec3_hash_.calculate(insecurechild_name) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
+TEST_P(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
// Similar to the previous case, but the delegation is an optout.
- mock_finder->setNSEC3Flag(true);
+ enableNSEC3(rrsets_to_add_);
- query.process(list,
+ query.process(*list_,
Name("foo.unsigned-delegation.example.com"),
qtype, response, true);
@@ -1202,44 +1313,46 @@ TEST_F(QueryTest, secureUnsignedDelegationWithNSEC3OptOut) {
NULL,
(string(unsigned_delegation_txt) +
string(nsec3_apex_txt) +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com")] +
+ nsec3_hash_.calculate(Name("www.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL);
}
-TEST_F(QueryTest, badSecureDelegation) {
+TEST_F(QueryTestForMockOnly, badSecureDelegation) {
+ // This is a broken data source scenario; works only with mock.
+
// Test whether exception is raised if DS query at delegation results in
// something different than SUCCESS or NXRRSET
- EXPECT_THROW(query.process(list,
+ EXPECT_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response, true), Query::BadDS);
// But only if DNSSEC is requested (it shouldn't even try to look for
// the DS otherwise)
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("bad-delegation.example.com"),
qtype, response));
}
-TEST_F(QueryTest, nxdomain) {
- EXPECT_NO_THROW(query.process(list,
+TEST_P(QueryTest, nxdomain) {
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC) {
+TEST_P(QueryTest, nxdomainWithNSEC) {
// NXDOMAIN with DNSSEC proof. We should have SOA, NSEC that proves
// NXDOMAIN and NSEC that proves nonexistence of matching wildcard,
// as well as their RRSIGs.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("nxdomain.example.com"), qtype,
response, true));
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
@@ -1255,12 +1368,18 @@ TEST_F(QueryTest, nxdomainWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC2) {
+TEST_P(QueryTest, nxdomainWithNSEC2) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about no_txt. In this case the best possible wildcard
// is derived from the next domain of the NSEC that proves NXDOMAIN, and
// the NSEC to provide the non existence of wildcard is different from
// the first NSEC.
- query.process(list, Name("(.no.example.com"), qtype, response,
+ query.process(*list_, Name("(.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1275,10 +1394,17 @@ TEST_F(QueryTest, nxdomainWithNSEC2) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
+TEST_P(QueryTest, nxdomainWithNSECDuplicate) {
+ // there seems to be a bug in the SQLite3 (or database in general) data
+ // source and this doesn't work. This is probably the same type of bug
+ // as nxdomainWithNSEC2 (Trac #2586).
+ if (GetParam() == SQLITE3) {
+ return;
+ }
+
// See comments about nz_txt. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence of wildcard.
- query.process(list, Name("nx.no.example.com"), qtype, response,
+ query.process(*list_, Name("nx.no.example.com"), qtype, response,
true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 4, 0,
NULL, (string(soa_txt) +
@@ -1290,52 +1416,62 @@ TEST_F(QueryTest, nxdomainWithNSECDuplicate) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC1) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC1) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with non NSEC RR.
mock_finder->setNSECResult(Name("badnsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("badnsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("badnsec.example.com"),
qtype, response, true),
std::bad_cast);
}
-TEST_F(QueryTest, nxdomainBadNSEC2) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC2) {
+ // This is a broken data source scenario; works only with mock.
+
// ZoneFinder::find() returns NXDOMAIN with an empty NSEC RR.
mock_finder->setNSECResult(Name("emptynsec.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("emptynsec.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("emptynsec.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC3) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns SUCCESS. it should be NXDOMAIN.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC4) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC4) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxdomainBadNSEC5) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC5) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns non NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->dname_rrset_);
// This is a bit odd, but we'll simply include the returned RRset.
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 6, 0,
NULL, (string(soa_txt) +
@@ -1350,28 +1486,30 @@ TEST_F(QueryTest, nxdomainBadNSEC5) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainBadNSEC6) {
+TEST_F(QueryTestForMockOnly, nxdomainBadNSEC6) {
+ // This is a broken data source scenario; works only with mock.
+
// "no-wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("*.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, nxrrset) {
- EXPECT_NO_THROW(query.process(list, Name("www.example.com"),
+TEST_P(QueryTest, nxrrset) {
+ EXPECT_NO_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
NULL, soa_txt, NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC) {
+TEST_P(QueryTest, nxrrsetWithNSEC) {
// NXRRSET with DNSSEC proof. We should have SOA, NSEC that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1383,7 +1521,7 @@ TEST_F(QueryTest, nxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, emptyNameWithNSEC) {
+TEST_P(QueryTest, emptyNameWithNSEC) {
// Empty non terminal with DNSSEC proof. This is one of the cases of
// Section 3.1.3.2 of RFC4035.
// mx.example.com. NSEC ).no.example.com. proves no.example.com. is a
@@ -1392,7 +1530,7 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
// exact match), so we only need one NSEC.
// From the point of the Query::process(), this is actually no different
// from the other NXRRSET case, but we check that explicitly just in case.
- query.process(list, Name("no.example.com"), RRType::A(),
+ query.process(*list_, Name("no.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1404,11 +1542,11 @@ TEST_F(QueryTest, emptyNameWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithoutNSEC) {
+TEST_P(QueryTest, nxrrsetWithoutNSEC) {
// NXRRSET with DNSSEC proof requested, but there's no NSEC at that node.
// This is an unexpected event (if the zone is supposed to be properly
// signed with NSECs), but we accept and ignore the oddity.
- query.process(list, Name("nonsec.example.com"), RRType::TXT(),
+ query.process(*list_, Name("nonsec.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -1417,10 +1555,10 @@ TEST_F(QueryTest, nxrrsetWithoutNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC) {
+TEST_P(QueryTest, wildcardNSEC) {
// The qname matches *.wild.example.com. The response should contain
// an NSEC that proves the non existence of a closer name.
- query.process(list, Name("www.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "www") +
@@ -1437,10 +1575,10 @@ TEST_F(QueryTest, wildcardNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC) {
+TEST_P(QueryTest, CNAMEwildNSEC) {
// Similar to the previous case, but the matching wildcard record is
// CNAME.
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
@@ -1453,17 +1591,17 @@ TEST_F(QueryTest, CNAMEwildNSEC) {
mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNSEC3) {
+TEST_P(QueryTest, wildcardNSEC3) {
// Similar to wildcardNSEC, but the zone is signed with NSEC3.
// The next closer is y.wild.example.com, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the apex.
- mock_finder->setNSEC3Flag(true);
-
- // This is NSEC3 for wild.example.com, which will be used in the middle
+ //
+ // Adding NSEC3 for wild.example.com, which will be used in the middle
// of identifying the next closer name.
- mock_finder->addRecord(nsec3_atwild_txt);
+ rrsets_to_add_.push_back(nsec3_atwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("x.y.wild.example.com"), RRType::A(),
+ query.process(*list_, Name("x.y.wild.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 6, 6,
(string(wild_txt).replace(0, 1, "x.y") +
@@ -1474,35 +1612,37 @@ TEST_F(QueryTest, wildcardNSEC3) {
getCommonRRSIGText("NS") + "\n" +
// NSEC3 for the wildcard proof and its RRSIG
string(nsec3_apex_txt) +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, CNAMEwildNSEC3) {
+TEST_P(QueryTest, CNAMEwildNSEC3) {
// Similar to CNAMEwildNSEC, but with NSEC3.
// The next closer is qname itself, the covering NSEC3 for it
// is (in our setup) the NSEC3 for the www.example.com.
- mock_finder->setNSEC3Flag(true);
- mock_finder->addRecord(nsec3_atcnamewild_txt);
+ rrsets_to_add_.push_back(nsec3_atcnamewild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www.cnamewild.example.com"),
+ query.process(*list_, Name("www.cnamewild.example.com"),
RRType::A(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(cnamewild_txt).replace(0, 1, "www") +
string("www.cnamewild.example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("CNAME") + "\n").c_str(),
(string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, // we are not interested in additionals in this test
mock_finder->getOrigin());
}
-TEST_F(QueryTest, badWildcardNSEC3) {
+TEST_F(QueryTestForMockOnly, badWildcardNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNSEC3, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1511,46 +1651,58 @@ TEST_F(QueryTest, badWildcardNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, badWildcardProof1) {
+TEST_F(QueryTestForMockOnly, badWildcardProof1) {
+ // This is a broken data source scenario; works only with mock.
+
// Unexpected case in wildcard proof: ZoneFinder::find() returns SUCCESS
// when NXDOMAIN is expected.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::SUCCESS,
mock_finder->dname_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof2) {
+TEST_F(QueryTestForMockOnly, badWildcardProof2) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" doesn't return RRset.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN, ConstRRsetPtr());
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, badWildcardProof3) {
+TEST_F(QueryTestForMockOnly, badWildcardProof3) {
+ // This is a broken data source scenario; works only with mock.
+
// "wildcard proof" returns empty NSEC.
mock_finder->setNSECResult(Name("www.wild.example.com"),
ZoneFinder::NXDOMAIN,
mock_finder->empty_nsec_rrset_);
- EXPECT_THROW(query.process(list, Name("www.wild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.wild.example.com"),
RRType::A(), response, true),
Query::BadNSEC);
}
-TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// NXRRSET on WILDCARD with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we only need one NSEC,
// which proves both NXDOMAIN and the non existence RRSETs of wildcard.
- query.process(list, Name("www.wild.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.wild.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -1562,12 +1714,18 @@ TEST_F(QueryTest, wildcardNxrrsetWithDuplicateNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC) {
+ // This is an in-memory specific bug (#2585), until it's fixed we
+ // tentatively skip the test for in-memory
+ if (GetParam() == INMEMORY) {
+ return;
+ }
+
// WILDCARD + NXRRSET with DNSSEC proof. We should have SOA, NSEC that
// proves the NXRRSET and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence RRSETs of
// wildcard.
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1582,15 +1740,15 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC) {
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
+TEST_P(QueryTest, wildcardNxrrsetWithNSEC3) {
// Similar to the previous case, but providing NSEC3 proofs according to
// RFC5155 Section 7.2.5.
- mock_finder->addRecord(nsec3_wild_txt);
- mock_finder->addRecord(nsec3_uwild_txt);
- mock_finder->setNSEC3Flag(true);
+ rrsets_to_add_.push_back(nsec3_wild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("www1.uwild.example.com"),
+ query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 8, 0, NULL,
@@ -1599,23 +1757,25 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_uwild_txt) +
- mock_finder->hash_map_[Name("uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_www_txt) +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(nsec3_wild_txt) +
- mock_finder->hash_map_[Name("*.uwild.example.com.")] +
+ nsec3_hash_.calculate(Name("*.uwild.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Collision) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -1624,15 +1784,17 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Collision) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
+TEST_F(QueryTestForMockOnly, wildcardNxrrsetWithNSEC3Broken) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to wildcardNxrrsetWithNSEC3, but no matching NSEC3 for the
// wildcard name will be returned. This shouldn't happen in a reasonably
- // NSEC-signed zone, and should result in an exception.
+ // NSEC3-signed zone, and should result in an exception.
mock_finder->setNSEC3Flag(true);
const Name wname("*.uwild.example.com.");
ZoneFinder::FindNSEC3Result nsec3(false, 0, textToRRset(nsec3_apex_txt),
@@ -1641,16 +1803,16 @@ TEST_F(QueryTest, wildcardNxrrsetWithNSEC3Broken) {
mock_finder->addRecord(nsec3_wild_txt);
mock_finder->addRecord(nsec3_uwild_txt);
- EXPECT_THROW(query.process(list, Name("www1.uwild.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www1.uwild.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, wildcardEmptyWithNSEC) {
+TEST_P(QueryTest, wildcardEmptyWithNSEC) {
// Empty WILDCARD with DNSSEC proof. We should have SOA, NSEC that proves
// the NXDOMAIN and their RRSIGs. In this case we need two NSEC RRs,
// one proves NXDOMAIN and the other proves non existence wildcard.
- query.process(list, Name("a.t.example.com"), RRType::A(),
+ query.process(*list_, Name("a.t.example.com"), RRType::A(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
@@ -1669,24 +1831,26 @@ TEST_F(QueryTest, wildcardEmptyWithNSEC) {
* This tests that when there's no SOA and we need a negative answer. It should
* throw in that case.
*/
-TEST_F(QueryTest, noSOA) {
+TEST_F(QueryTestForMockOnly, noSOA) {
+ // This is a broken data source scenario; works only with mock.
+
// disable zone's SOA RR.
mock_finder->setSOAFlag(false);
// The NX Domain
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
qtype, response), Query::NoSOA);
// Of course, we don't look into the response, as it throwed
// NXRRSET
- EXPECT_THROW(query.process(list, Name("nxrrset.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxrrset.example.com"),
qtype, response), Query::NoSOA);
}
-TEST_F(QueryTest, noMatchZone) {
+TEST_P(QueryTest, noMatchZone) {
// there's a zone in the memory datasource but it doesn't match the qname.
// should result in REFUSED.
- query.process(list, Name("example.org"), qtype, response);
+ query.process(*list_, Name("example.org"), qtype, response);
EXPECT_EQ(Rcode::REFUSED(), response.getRcode());
}
@@ -1696,8 +1860,8 @@ TEST_F(QueryTest, noMatchZone) {
* The MX RRset has two RRs, one pointing to a known domain with
* A record, other to unknown out of zone one.
*/
-TEST_F(QueryTest, MX) {
- query.process(list, Name("mx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MX) {
+ query.process(*list_, Name("mx.example.com"), RRType::MX(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
@@ -1710,8 +1874,8 @@ TEST_F(QueryTest, MX) {
*
* This should not trigger the additional processing for the exchange.
*/
-TEST_F(QueryTest, MXAlias) {
- query.process(list, Name("cnamemx.example.com"), RRType::MX(),
+TEST_P(QueryTest, MXAlias) {
+ query.process(*list_, Name("cnamemx.example.com"), RRType::MX(),
response);
// there shouldn't be no additional RRs for the exchanges (we have 3
@@ -1730,69 +1894,69 @@ TEST_F(QueryTest, MXAlias) {
* TODO: We currently don't do chaining, so only the CNAME itself should be
* returned.
*/
-TEST_F(QueryTest, CNAME) {
- query.process(list, Name("cname.example.com"), RRType::A(),
+TEST_P(QueryTest, CNAME) {
+ query.process(*list_, Name("cname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME) {
+TEST_P(QueryTest, explicitCNAME) {
// same owner name as the CNAME test but explicitly query for CNAME RR.
// expect the same response as we don't provide a full chain yet.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_RRSET) {
+TEST_P(QueryTest, CNAME_NX_RRSET) {
// Leads to www.example.com, it doesn't have TXT
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional.
- query.process(list, Name("cname.example.com"), RRType::TXT(),
+ query.process(*list_, Name("cname.example.com"), RRType::TXT(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_RRSET) {
+TEST_P(QueryTest, explicitCNAME_NX_RRSET) {
// same owner name as the NXRRSET test but explicitly query for CNAME RR.
- query.process(list, Name("cname.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cname.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_NX_DOMAIN) {
+TEST_P(QueryTest, CNAME_NX_DOMAIN) {
// Leads to nxdomain.example.com
// note: with chaining, what should be expected is not trivial:
// BIND 9 returns the CNAME in answer and SOA in authority, no additional,
// RCODE being NXDOMAIN.
// NSD returns the CNAME, NS in authority, A/AAAA for NS in additional,
// RCODE being NOERROR.
- query.process(list, Name("cnamenxdom.example.com"), RRType::A(),
+ query.process(*list_, Name("cnamenxdom.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_nxdom_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_NX_DOMAIN) {
+TEST_P(QueryTest, explicitCNAME_NX_DOMAIN) {
// same owner name as the NXDOMAIN test but explicitly query for CNAME RR.
- query.process(list, Name("cnamenxdom.example.com"),
+ query.process(*list_, Name("cnamenxdom.example.com"),
RRType::CNAME(), response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
cname_nxdom_txt, zone_ns_txt, ns_addrs_txt);
}
-TEST_F(QueryTest, CNAME_OUT) {
+TEST_P(QueryTest, CNAME_OUT) {
/*
* This leads out of zone. This should have only the CNAME even
* when we do chaining.
@@ -1801,16 +1965,16 @@ TEST_F(QueryTest, CNAME_OUT) {
* Then the same test should be done with .org included there and
* see what it does (depends on what we want to do)
*/
- query.process(list, Name("cnameout.example.com"), RRType::A(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 0, 0,
cname_out_txt, NULL, NULL);
}
-TEST_F(QueryTest, explicitCNAME_OUT) {
+TEST_P(QueryTest, explicitCNAME_OUT) {
// same owner name as the OUT test but explicitly query for CNAME RR.
- query.process(list, Name("cnameout.example.com"), RRType::CNAME(),
+ query.process(*list_, Name("cnameout.example.com"), RRType::CNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1825,8 +1989,8 @@ TEST_F(QueryTest, explicitCNAME_OUT) {
* as well. This includes tests pointing inside the zone, outside the zone,
* pointing to NXRRSET and NXDOMAIN cases (similarly as with CNAME).
*/
-TEST_F(QueryTest, DNAME) {
- query.process(list, Name("www.dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1841,8 +2005,8 @@ TEST_F(QueryTest, DNAME) {
* ANY is handled specially sometimes. We check it is not the case with
* DNAME.
*/
-TEST_F(QueryTest, DNAME_ANY) {
- query.process(list, Name("www.dname.example.com"), RRType::ANY(),
+TEST_P(QueryTest, DNAME_ANY) {
+ query.process(*list_, Name("www.dname.example.com"), RRType::ANY(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 0, 0,
@@ -1850,8 +2014,8 @@ TEST_F(QueryTest, DNAME_ANY) {
}
// Test when we ask for DNAME explicitly, it does no synthetizing.
-TEST_F(QueryTest, explicitDNAME) {
- query.process(list, Name("dname.example.com"), RRType::DNAME(),
+TEST_P(QueryTest, explicitDNAME) {
+ query.process(*list_, Name("dname.example.com"), RRType::DNAME(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1862,8 +2026,8 @@ TEST_F(QueryTest, explicitDNAME) {
* Request a RRset at the domain with DNAME. It should not synthetize
* the CNAME, it should return the RRset.
*/
-TEST_F(QueryTest, DNAME_A) {
- query.process(list, Name("dname.example.com"), RRType::A(),
+TEST_P(QueryTest, DNAME_A) {
+ query.process(*list_, Name("dname.example.com"), RRType::A(),
response);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 1, 3, 3,
@@ -1874,8 +2038,8 @@ TEST_F(QueryTest, DNAME_A) {
* Request a RRset at the domain with DNAME that is not there (NXRRSET).
* It should not synthetize the CNAME.
*/
-TEST_F(QueryTest, DNAME_NX_RRSET) {
- EXPECT_NO_THROW(query.process(list, Name("dname.example.com"),
+TEST_P(QueryTest, DNAME_NX_RRSET) {
+ EXPECT_NO_THROW(query.process(*list_, Name("dname.example.com"),
RRType::TXT(), response));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 1, 0,
@@ -1887,7 +2051,7 @@ TEST_F(QueryTest, DNAME_NX_RRSET) {
* however, should not throw (and crash the server), but respond with
* YXDOMAIN.
*/
-TEST_F(QueryTest, LongDNAME) {
+TEST_P(QueryTest, LongDNAME) {
// A name that is as long as it can be
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
@@ -1895,7 +2059,7 @@ TEST_F(QueryTest, LongDNAME) {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
responseCheck(response, Rcode::YXDOMAIN(), AA_FLAG, 1, 0, 0,
@@ -1907,14 +2071,14 @@ TEST_F(QueryTest, LongDNAME) {
* This tests that we don't reject valid one by some kind of off by
* one mistake.
*/
-TEST_F(QueryTest, MaxLenDNAME) {
+TEST_P(QueryTest, MaxLenDNAME) {
Name longname(
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa."
"dname.example.com.");
- EXPECT_NO_THROW(query.process(list, longname, RRType::A(),
+ EXPECT_NO_THROW(query.process(*list_, longname, RRType::A(),
response));
// Check the answer is OK
@@ -1963,7 +2127,10 @@ nsec3Check(bool expected_matched, uint8_t expected_labels,
actual_rrsets.end());
}
-TEST_F(QueryTest, findNSEC3) {
+TEST_F(QueryTestForMockOnly, findNSEC3) {
+ // This test is intended to test the mock data source behavior; no need
+ // to do it for others.
+
// In all test cases in the recursive mode, the closest encloser is the
// apex, and result's closest_labels should be the number of apex labels.
// (In non recursive mode closest_labels should be the # labels of the
@@ -1975,7 +2142,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, non recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), false));
}
@@ -1983,7 +2150,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("apex, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("example.com"), true));
}
@@ -1992,7 +2159,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, non recursive");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"),
false));
}
@@ -2002,7 +2169,7 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, recursive");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain.example.com"), true));
}
@@ -2012,7 +2179,8 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("nxdomain, next closer != qname");
nsec3Check(true, expected_closest_labels,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt + "\n" +
+ string(nsec3_apex_txt) + "\n" +
+ nsec3_apex_rrsig_txt + "\n" +
nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nx.domain.example.com"),
true));
@@ -2022,14 +2190,14 @@ TEST_F(QueryTest, findNSEC3) {
{
SCOPED_TRACE("largest");
nsec3Check(false, 4,
- nsec3_apex_txt + "\n" + nsec3_apex_rrsig_txt,
+ string(nsec3_apex_txt) + "\n" + nsec3_apex_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain2.example.com"),
false));
}
{
SCOPED_TRACE("smallest");
nsec3Check(false, 4,
- nsec3_www_txt + "\n" + nsec3_www_rrsig_txt,
+ string(nsec3_www_txt) + "\n" + nsec3_www_rrsig_txt,
mock_finder->findNSEC3(Name("nxdomain3.example.com"),
false));
}
@@ -2100,14 +2268,17 @@ private:
const bool have_ds_;
};
-TEST_F(QueryTest, dsAboveDelegation) {
+TEST_F(QueryTestForMockOnly, dsAboveDelegation) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
memory_client.addZone(ZoneFinderPtr(new AlternateZoneFinder(
Name("delegation.example.com"))));
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("delegation.example.com"),
RRType::DS(), response, true));
@@ -2121,7 +2292,7 @@ TEST_F(QueryTest, dsAboveDelegation) {
ns_addrs_and_sig_txt.c_str());
}
-TEST_F(QueryTest, dsAboveDelegationNoData) {
+TEST_P(QueryTest, dsAboveDelegationNoData) {
// Similar to the previous case, but the query is for an unsigned zone
// (which doesn't have a DS at the parent). The response should be a
// "no data" error. The query should still be handled at the parent.
@@ -2131,7 +2302,7 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// The following will succeed only if the search goes to the parent
// zone, not the child one we added above.
- EXPECT_NO_THROW(query.process(list,
+ EXPECT_NO_THROW(query.process(*list_,
Name("unsigned-delegation.example.com"),
RRType::DS(), response, true));
@@ -2148,8 +2319,8 @@ TEST_F(QueryTest, dsAboveDelegationNoData) {
// This one checks that type-DS query results in a "no data" response
// when it happens to be sent to the child zone, as described in RFC 4035,
// section 3.1.4.1. The example is inspired by the B.8. example from the RFC.
-TEST_F(QueryTest, dsBelowDelegation) {
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegation) {
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
@@ -2164,9 +2335,10 @@ TEST_F(QueryTest, dsBelowDelegation) {
// Similar to the previous case, but even more pathological: the DS somehow
// exists in the child zone. The Query module should still return SOA.
// In our implementation NSEC/NSEC3 isn't attached in this case.
-TEST_F(QueryTest, dsBelowDelegationWithDS) {
- mock_finder->addRecord(zone_ds_txt); // add the DS to the child's apex
- EXPECT_NO_THROW(query.process(list, Name("example.com"),
+TEST_P(QueryTest, dsBelowDelegationWithDS) {
+ rrsets_to_add_.push_back(zone_ds_txt);
+ addRRsets(rrsets_to_add_, *list_, base_zone_file);
+ EXPECT_NO_THROW(query.process(*list_, Name("example.com"),
RRType::DS(), response, true));
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 2, 0, NULL,
@@ -2178,16 +2350,16 @@ TEST_F(QueryTest, dsBelowDelegationWithDS) {
// DS query received at a completely irrelevant (neither parent nor child)
// server. It should just like the "noZone" test case, but DS query involves
// special processing, so we test it explicitly.
-TEST_F(QueryTest, dsNoZone) {
- query.process(list, Name("example"), RRType::DS(), response,
+TEST_P(QueryTest, dsNoZone) {
+ query.process(*list_, Name("example"), RRType::DS(), response,
true);
responseCheck(response, Rcode::REFUSED(), 0, 0, 0, 0, NULL, NULL, NULL);
}
// DS query for a "grandchild" zone. This should result in normal
// delegation (unless this server also has authority of the grandchild zone).
-TEST_F(QueryTest, dsAtGrandParent) {
- query.process(list, Name("grand.delegation.example.com"),
+TEST_P(QueryTest, dsAtGrandParent) {
+ query.process(*list_, Name("grand.delegation.example.com"),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), 0, 0, 6, 6, NULL,
(string(delegation_txt) + string(delegation_ds_txt) +
@@ -2201,12 +2373,15 @@ TEST_F(QueryTest, dsAtGrandParent) {
// side and should result in no data with SOA. Note that the server doesn't
// have authority for the "parent". Unlike the dsAboveDelegation test case
// the query should be handled in the child zone, not in the grandparent.
-TEST_F(QueryTest, dsAtGrandParentAndChild) {
+TEST_F(QueryTestForMockOnly, dsAtGrandParentAndChild) {
+ // We could setup the child zone for other data sources, but it won't be
+ // simple addition. For now we test it for mock only.
+
// Pretending to have authority for the child zone, too.
const Name childname("grand.delegation.example.com");
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(childname)));
- query.process(list, childname, RRType::DS(), response, true);
+ query.process(*list_, childname, RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(childname.toText() + " 3600 IN SOA . . 0 0 0 0 0\n" +
childname.toText() + " 3600 IN RRSIG " +
@@ -2220,11 +2395,14 @@ TEST_F(QueryTest, dsAtGrandParentAndChild) {
// DS query for the root name (quite pathological). Since there's no "parent",
// the query will be handled in the root zone anyway, and should (normally)
// result in no data.
-TEST_F(QueryTest, dsAtRoot) {
+TEST_F(QueryTestForMockOnly, dsAtRoot) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
// Pretend to be a root server.
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME())));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(". 3600 IN SOA . . 0 0 0 0 0\n") +
@@ -2237,11 +2415,14 @@ TEST_F(QueryTest, dsAtRoot) {
// Even more pathological case: A faked root zone actually has its own DS
// query. How we respond wouldn't matter much in practice, but check if
// it behaves as it's intended. This implementation should return the DS.
-TEST_F(QueryTest, dsAtRootWithDS) {
+TEST_F(QueryTestForMockOnly, dsAtRootWithDS) {
+ // We could setup the additional zone for other data sources, but it
+ // won't be simple addition. For now we test it for mock only.
+
memory_client.addZone(ZoneFinderPtr(
new AlternateZoneFinder(Name::ROOT_NAME(),
true)));
- query.process(list, Name::ROOT_NAME(), RRType::DS(), response,
+ query.process(*list_, Name::ROOT_NAME(), RRType::DS(), response,
true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 2, 2, 0,
(string(". 3600 IN DS 57855 5 1 49FD46E6C4B45C55D4AC69CBD"
@@ -2253,19 +2434,19 @@ TEST_F(QueryTest, dsAtRootWithDS) {
}
// Check the signature is present when an NXRRSET is returned
-TEST_F(QueryTest, nxrrsetWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
// NXRRSET with DNSSEC proof. We should have SOA, NSEC3 that proves the
// NXRRSET and their RRSIGs.
- query.process(list, Name("www.example.com"), RRType::TXT(),
+ query.process(*list_, Name("www.example.com"), RRType::TXT(),
response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_www_txt) + "\n" +
- mock_finder->hash_map_[Name("www.example.com.")] +
+ nsec3_hash_.calculate(Name("www.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
@@ -2273,7 +2454,9 @@ TEST_F(QueryTest, nxrrsetWithNSEC3) {
// Check the exception is correctly raised when the NSEC3 thing isn't in the
// zone
-TEST_F(QueryTest, nxrrsetMissingNSEC3) {
+TEST_F(QueryTestForMockOnly, nxrrsetMissingNSEC3) {
+ // This is a broken data source scenario; works only with mock.
+
mock_finder->setNSEC3Flag(true);
// We just need it to return false for "matched". This indicates
// there's no exact match for NSEC3 on www.example.com.
@@ -2281,67 +2464,67 @@ TEST_F(QueryTest, nxrrsetMissingNSEC3) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("www.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("www.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, but does have a matching NSEC3 record
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation.example.com."),
+ query.process(*list_, Name("unsigned-delegation.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 4, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
- mock_finder->setNSEC3Flag(true);
+TEST_P(QueryTest, nxrrsetWithNSEC3_ds_no_exact) {
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ enableNSEC3(rrsets_to_add_);
// This delegation has no DS, and no directly matching NSEC3 record
// So the response should contain closest encloser proof (and the
// 'next closer' should have opt-out set, though that is not
// actually checked)
// (See RFC5155 section 7.2.4)
- query.process(list, Name("unsigned-delegation-optout.example.com."),
+ query.process(*list_, Name("unsigned-delegation-optout.example.com."),
RRType::DS(), response, true);
responseCheck(response, Rcode::NOERROR(), AA_FLAG, 0, 6, 0, NULL,
(string(soa_txt) + string("example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("SOA") + "\n" +
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[Name("example.com.")] +
+ nsec3_hash_.calculate(Name("example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
string(unsigned_delegation_nsec3_txt) + "\n" +
- mock_finder->
- hash_map_[Name("unsigned-delegation.example.com.")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com.")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n").c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
+TEST_P(QueryTest, nxdomainWithNSEC3Proof) {
// Name Error (NXDOMAIN) case with NSEC3 proof per RFC5155 Section 7.2.2.
- // Enable NSEC3
- mock_finder->setNSEC3Flag(true);
// This will be the covering NSEC3 for the next closer
- mock_finder->addRecord(nsec3_uwild_txt);
+ rrsets_to_add_.push_back(nsec3_uwild_txt);
// This will be the covering NSEC3 for the possible wildcard
- mock_finder->addRecord(unsigned_delegation_nsec3_txt);
+ rrsets_to_add_.push_back(unsigned_delegation_nsec3_txt);
+ // Enable NSEC3
+ enableNSEC3(rrsets_to_add_);
- query.process(list, Name("nxdomain.example.com"), qtype,
+ query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true);
responseCheck(response, Rcode::NXDOMAIN(), AA_FLAG, 0, 8, 0, NULL,
// SOA + its RRSIG
@@ -2350,24 +2533,26 @@ TEST_F(QueryTest, nxdomainWithNSEC3Proof) {
getCommonRRSIGText("SOA") + "\n" +
// NSEC3 for the closest encloser + its RRSIG
string(nsec3_apex_txt) + "\n" +
- mock_finder->hash_map_[mock_finder->getOrigin()] +
+ nsec3_hash_.calculate(mock_finder->getOrigin()) +
string(".example.com. 3600 IN RRSIG ") +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the next closer + its RRSIG
string(nsec3_uwild_txt) + "\n" +
- mock_finder->hash_map_[Name("uwild.example.com")] +
+ nsec3_hash_.calculate(Name("uwild.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3") + "\n" +
// NSEC3 for the wildcard + its RRSIG
string(unsigned_delegation_nsec3_txt) +
- mock_finder->hash_map_[
- Name("unsigned-delegation.example.com")] +
+ nsec3_hash_.calculate(
+ Name("unsigned-delegation.example.com")) +
".example.com. 3600 IN RRSIG " +
getCommonRRSIGText("NSEC3")).c_str(),
NULL, mock_finder->getOrigin());
}
-TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadNextNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to the previous case, but emulating run time collision by
// returning NULL in the next closer proof for the closest encloser
// proof.
@@ -2376,12 +2561,14 @@ TEST_F(QueryTest, nxdomainWithBadNextNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"),
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"),
RRType::TXT(), response, true),
Query::BadNSEC3);
}
-TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
+TEST_F(QueryTestForMockOnly, nxdomainWithBadWildcardNSEC3Proof) {
+ // This is a broken data source scenario; works only with mock.
+
// Similar to nxdomainWithNSEC3Proof, but let findNSEC3() return a matching
// NSEC3 for the possible wildcard name, emulating run-time collision.
// This should result in BadNSEC3 exception.
@@ -2395,7 +2582,7 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
ConstRRsetPtr());
mock_finder->setNSEC3Result(&nsec3, &wname);
- EXPECT_THROW(query.process(list, Name("nxdomain.example.com"), qtype,
+ EXPECT_THROW(query.process(*list_, Name("nxdomain.example.com"), qtype,
response, true),
Query::BadNSEC3);
}
@@ -2403,10 +2590,13 @@ TEST_F(QueryTest, nxdomainWithBadWildcardNSEC3Proof) {
// The following are tentative tests until we really add tests for the
// query logic for these cases. At that point it's probably better to
// clean them up.
-TEST_F(QueryTest, emptyNameWithNSEC3) {
- mock_finder->setNSEC3Flag(true);
- ZoneFinderContextPtr result = mock_finder->find(
- Name("no.example.com"), RRType::A(), ZoneFinder::FIND_DNSSEC);
+TEST_P(QueryTest, emptyNameWithNSEC3) {
+ enableNSEC3(rrsets_to_add_);
+ const Name qname("no.example.com");
+ ASSERT_TRUE(list_->find(qname).finder_);
+ ZoneFinderContextPtr result =
+ list_->find(qname).finder_->find(qname, RRType::A(),
+ ZoneFinder::FIND_DNSSEC);
EXPECT_EQ(ZoneFinder::NXRRSET, result->code);
EXPECT_FALSE(result->rrset);
EXPECT_TRUE(result->isNSEC3Signed());
@@ -2447,7 +2637,9 @@ loadRRsetVector() {
loadRRsetVectorCallback);
}
-TEST_F(QueryTest, DuplicateNameRemoval) {
+// Note: this is an independent test; don't have to be in the QueryTest
+// fixture.
+TEST(QueryTestSingle, DuplicateNameRemoval) {
// Load some RRsets into the master vector.
loadRRsetVector();
diff --git a/src/bin/auth/tests/testdata/.gitignore b/src/bin/auth/tests/testdata/.gitignore
index b2e0e50..37acc8a 100644
--- a/src/bin/auth/tests/testdata/.gitignore
+++ b/src/bin/auth/tests/testdata/.gitignore
@@ -6,3 +6,13 @@
/shortanswer_fromWire.wire
/simplequery_fromWire.wire
/simpleresponse_fromWire.wire
+/example-base.sqlite3
+/example-base.sqlite3.copied
+/example-base.zone
+/example-base.zone
+/example-common-inc.zone
+/example-nsec3-inc.zone
+/example-nsec3.sqlite3
+/example-nsec3.sqlite3.copied
+/example-nsec3.zone
+/example.zone
diff --git a/src/bin/auth/tests/testdata/Makefile.am b/src/bin/auth/tests/testdata/Makefile.am
index a4ea1a5..fed498a 100644
--- a/src/bin/auth/tests/testdata/Makefile.am
+++ b/src/bin/auth/tests/testdata/Makefile.am
@@ -1,4 +1,6 @@
-CLEANFILES = *.wire
+CLEANFILES = *.wire *.copied
+CLEANFILES += example-base.sqlite3 example-nsec3.sqlite3
+CLEANFILES += example-common-inc.zone
BUILT_SOURCES = badExampleQuery_fromWire.wire examplequery_fromWire.wire
BUILT_SOURCES += iqueryresponse_fromWire.wire multiquestion_fromWire.wire
@@ -24,5 +26,8 @@ EXTRA_DIST += example.com
EXTRA_DIST += example.zone
EXTRA_DIST += example.sqlite3
+EXTRA_DIST += example-base-inc.zone example-nsec3-inc.zone
+EXTRA_DIST += example-common-inc-template.zone
+
.spec.wire:
$(PYTHON) $(top_builddir)/src/lib/util/python/gen_wiredata.py -o $@ $<
diff --git a/src/bin/auth/tests/testdata/example-base-inc.zone b/src/bin/auth/tests/testdata/example-base-inc.zone
new file mode 100644
index 0000000..bbcbef1
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base-inc.zone
@@ -0,0 +1,236 @@
+;; This file defines a set of RRs commonly used in query tests in the
+;; form of standard master zone file.
+;;
+;; It's a sequence of the following pattern:
+;; ;var=<var_name>
+;; RR_1
+;; RR_2
+;; ..
+;; RR_n
+;;
+;; where var_name is a string that can be used as a variable name in a
+;; C/C++ source file or an empty string. RR_x is a single-line
+;; textual representation of an arbitrary DNS RR.
+;;
+;; If var_name is non empty, the generator script will define a C
+;; variable of C-string type for that set of RRs so that it can be referred
+;; to in the test source file.
+;;
+;; Note that lines beginning ';var=' is no different from other
+;; comment lines as a zone file. It has special meaning only for the
+;; generator script. Obviously, real comment lines cannot begin with
+;; ';var=' (which should be less likely to happen in practice though).
+;;
+;; These RRs will be loaded into in-memory data source in that order.
+;; Note that it may impose stricter restriction on the order of RRs.
+;; In general, each RRset of the same name and type and its RRSIG (if
+;; any) is expected to be grouped.
+
+;var=soa_txt
+example.com. 3600 IN SOA . . 1 0 0 0 0
+;var=zone_ns_txt
+example.com. 3600 IN NS glue.delegation.example.com.
+example.com. 3600 IN NS noglue.example.com.
+example.com. 3600 IN NS example.net.
+
+;var=
+example.com. 3600 IN RRSIG SOA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+example.com. 3600 IN RRSIG NS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Note: the position of the next RR is tricky. It's placed here to
+;; be grouped with the subsequent A RR of the name. But we also want
+;; to group the A RR with other RRs of a different owner name, so the RRSIG
+;; cannot be placed after the A RR. The empty 'var=' specification is
+;; not necessary here, but in case we want to reorganize the ordering
+;; (in which case it's more likely to be needed), we keep it here.
+;var=
+noglue.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=ns_addrs_txt
+noglue.example.com. 3600 IN A 192.0.2.53
+glue.delegation.example.com. 3600 IN A 192.0.2.153
+glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
+
+;var=
+glue.delegation.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+glue.delegation.example.com. 3600 IN RRSIG AAAA 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=delegation_txt
+delegation.example.com. 3600 IN NS glue.delegation.example.com.
+delegation.example.com. 3600 IN NS noglue.example.com.
+delegation.example.com. 3600 IN NS cname.example.com.
+delegation.example.com. 3600 IN NS example.org.
+
+;var=
+delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Borrowed from the RFC4035
+;var=delegation_ds_txt
+delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
+;var=mx_txt
+mx.example.com. 3600 IN MX 10 www.example.com.
+mx.example.com. 3600 IN MX 20 mailer.example.org.
+mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
+;var=www_a_txt
+www.example.com. 3600 IN A 192.0.2.80
+
+;var=
+www.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cname_txt
+cname.example.com. 3600 IN CNAME www.example.com.
+;var=cname_nxdom_txt
+cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
+;; CNAME Leading out of zone
+;var=cname_out_txt
+cnameout.example.com. 3600 IN CNAME www.example.org.
+;; The DNAME to do tests against
+;var=dname_txt
+dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
+;; Some data at the dname node (allowed by RFC 2672)
+;var=dname_a_txt
+dname.example.com. 3600 IN A 192.0.2.5
+;; This is not inside the zone, this is created at runtime
+;; www.dname.example.com. 3600 IN CNAME www.somethinglong.dnametarget.example.com.
+;; The rest of data won't be referenced from the test cases.
+;var=other_zone_rrs
+cnamemailer.example.com. 3600 IN CNAME www.example.com.
+cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
+mx.delegation.example.com. 3600 IN A 192.0.2.100
+;; Wildcards
+;var=wild_txt
+*.wild.example.com. 3600 IN A 192.0.2.7
+;var=nsec_wild_txt
+*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
+
+;var=
+*.wild.example.com. 3600 IN RRSIG A 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.wild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=cnamewild_txt
+*.cnamewild.example.com. 3600 IN CNAME www.example.org.
+;var=nsec_cnamewild_txt
+*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
+
+;var=
+*.cnamewild.example.com. 3600 IN RRSIG CNAME 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+*.cnamewild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Wildcard_nxrrset
+;var=wild_txt_nxrrset
+*.uwild.example.com. 3600 IN A 192.0.2.9
+;var=nsec_wild_txt_nxrrset
+*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
+;var=
+*.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=wild_txt_next
+www.uwild.example.com. 3600 IN A 192.0.2.11
+;var=
+www.uwild.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_wild_txt_next
+www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
+;; Wildcard empty
+;var=empty_txt
+b.*.t.example.com. 3600 IN A 192.0.2.13
+;var=nsec_empty_txt
+b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
+
+;var=
+b.*.t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=empty_prev_txt
+t.example.com. 3600 IN A 192.0.2.15
+;var=nsec_empty_prev_txt
+t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
+
+;var=
+t.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Used in NXDOMAIN proof test. We are going to test some unusual case where
+;; the best possible wildcard is below the "next domain" of the NSEC RR that
+;; proves the NXDOMAIN, i.e.,
+;; mx.example.com. (exist)
+;; (.no.example.com. (qname, NXDOMAIN)
+;; ).no.example.com. (exist)
+;; *.no.example.com. (best possible wildcard, not exist)
+;var=no_txt
+\).no.example.com. 3600 IN AAAA 2001:db8::53
+;; NSEC records.
+;var=nsec_apex_txt
+example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
+;var=
+example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec_mx_txt
+mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
+
+;var=
+mx.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;var=nsec_no_txt
+\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
+
+;var=
+\).no.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
+;; non existence of wildcard. The following records will be used for that
+;; test.
+;; ).no.example.com. (exist, whose NSEC proves everything)
+;; *.no.example.com. (best possible wildcard, not exist)
+;; nx.no.example.com. (NXDOMAIN)
+;; nz.no.example.com. (exist)
+;var=nz_txt
+nz.no.example.com. 3600 IN AAAA 2001:db8::5300
+;var=nsec_nz_txt
+nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
+;var=nsec_nxdomain_txt
+noglue.example.com. 3600 IN NSEC nonsec.example.com. A
+
+;var=
+noglue.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; NSEC for the normal NXRRSET case
+;var=nsec_www_txt
+www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
+
+;var=
+www.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Authoritative data without NSEC
+;var=nonsec_a_txt
+nonsec.example.com. 3600 IN A 192.0.2.0
+
+;; (Secure) delegation data; Delegation with DS record
+;var=signed_delegation_txt
+signed-delegation.example.com. 3600 IN NS ns.example.net.
+;var=signed_delegation_ds_txt
+signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
+
+;var=
+signed-delegation.example.com. 3600 IN RRSIG DS 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; (Secure) delegation data; Delegation without DS record (and both NSEC
+;; and NSEC3 denying its existence)
+;var=unsigned_delegation_txt
+unsigned-delegation.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_nsec_txt
+unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
+
+;var=
+unsigned-delegation.example.com. 3600 IN RRSIG NSEC 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+
+;; Delegation without DS record, and no direct matching NSEC3 record
+;var=unsigned_delegation_optout_txt
+unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
+;var=unsigned_delegation_optout_nsec_txt
+unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
+
+;; (Secure) delegation data; Delegation where the DS lookup will raise an
+;; exception.
+;var=bad_delegation_txt
+bad-delegation.example.com. 3600 IN NS ns.example.net.
+
+;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
+;; or NSEC3 that proves it.
+;var=nosec_delegation_txt
+nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example-base.zone.in b/src/bin/auth/tests/testdata/example-base.zone.in
new file mode 100644
index 0000000..63d2af0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-base.zone.in
@@ -0,0 +1,7 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests.
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example-common-inc-template.zone b/src/bin/auth/tests/testdata/example-common-inc-template.zone
new file mode 100644
index 0000000..d7259bf
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-common-inc-template.zone
@@ -0,0 +1,5 @@
+;;
+;; This is an initial template of part of test zone file used in query test
+;; and expected to be included from other zone files. This is
+;; intentionally kept empty.
+;;
diff --git a/src/bin/auth/tests/testdata/example-nsec3-inc.zone b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
new file mode 100644
index 0000000..7742df0
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3-inc.zone
@@ -0,0 +1,16 @@
+;; See query_testzone_data.txt for general notes.
+
+;; NSEC3PARAM. This is needed for database-based data source to
+;; signal the zone is NSEC3-signed
+;var=
+example.com. 3600 IN NSEC3PARAM 1 1 12 aabbccdd
+
+;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
+;var=nsec3_apex_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
+;var=nsec3_apex_rrsig_txt
+0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
+;var=nsec3_www_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
+;var=nsec3_www_rrsig_txt
+q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
diff --git a/src/bin/auth/tests/testdata/example-nsec3.zone.in b/src/bin/auth/tests/testdata/example-nsec3.zone.in
new file mode 100644
index 0000000..aaf3d6a
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example-nsec3.zone.in
@@ -0,0 +1,8 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests including NSEC3 records, making the zone is "NSEC3 signed".
+;;
+
+$INCLUDE @abs_srcdir@/example-base-inc.zone
+$INCLUDE @abs_srcdir@/example-nsec3-inc.zone
+$INCLUDE @abs_builddir@/example-common-inc.zone
diff --git a/src/bin/auth/tests/testdata/example.zone b/src/bin/auth/tests/testdata/example.zone
deleted file mode 100644
index af0b618..0000000
--- a/src/bin/auth/tests/testdata/example.zone
+++ /dev/null
@@ -1,121 +0,0 @@
-;;
-;; This is a complete (but crafted and somewhat broken) zone file used
-;; in query tests.
-;;
-
-example.com. 3600 IN SOA . . 0 0 0 0 0
-example.com. 3600 IN NS glue.delegation.example.com.
-example.com. 3600 IN NS noglue.example.com.
-example.com. 3600 IN NS example.net.
-example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-glue.delegation.example.com. 3600 IN A 192.0.2.153
-glue.delegation.example.com. 3600 IN AAAA 2001:db8::53
-noglue.example.com. 3600 IN A 192.0.2.53
-delegation.example.com. 3600 IN NS glue.delegation.example.com.
-delegation.example.com. 3600 IN NS noglue.example.com.
-delegation.example.com. 3600 IN NS cname.example.com.
-delegation.example.com. 3600 IN NS example.org.
-;; Borrowed from the RFC4035
-delegation.example.com. 3600 IN DS 57855 5 1 B6DCD485719ADCA18E5F3D48A2331627FDD3 636B
-mx.example.com. 3600 IN MX 10 www.example.com.
-mx.example.com. 3600 IN MX 20 mailer.example.org.
-mx.example.com. 3600 IN MX 30 mx.delegation.example.com.
-www.example.com. 3600 IN A 192.0.2.80
-cname.example.com. 3600 IN CNAME www.example.com.
-cnamenxdom.example.com. 3600 IN CNAME nxdomain.example.com.
-;; CNAME Leading out of zone
-cnameout.example.com. 3600 IN CNAME www.example.org.
-;; The DNAME to do tests against
-dname.example.com. 3600 IN DNAME somethinglong.dnametarget.example.com.
-;; Some data at the dname node (allowed by RFC 2672)
-dname.example.com. 3600 IN A 192.0.2.5
-;; The rest of data won't be referenced from the test cases.
-cnamemailer.example.com. 3600 IN CNAME www.example.com.
-cnamemx.example.com. 3600 IN MX 10 cnamemailer.example.com.
-mx.delegation.example.com. 3600 IN A 192.0.2.100
-;; Wildcards
-*.wild.example.com. 3600 IN A 192.0.2.7
-*.wild.example.com. 3600 IN NSEC www.example.com. A NSEC RRSIG
-*.cnamewild.example.com. 3600 IN CNAME www.example.org.
-*.cnamewild.example.com. 3600 IN NSEC delegation.example.com. CNAME NSEC RRSIG
-;; Wildcard_nxrrset
-*.uwild.example.com. 3600 IN A 192.0.2.9
-*.uwild.example.com. 3600 IN NSEC www.uwild.example.com. A NSEC RRSIG
-www.uwild.example.com. 3600 IN A 192.0.2.11
-www.uwild.example.com. 3600 IN NSEC *.wild.example.com. A NSEC RRSIG
-;; Wildcard empty
-b.*.t.example.com. 3600 IN A 192.0.2.13
-b.*.t.example.com. 3600 IN NSEC *.uwild.example.com. A NSEC RRSIG
-t.example.com. 3600 IN A 192.0.2.15
-t.example.com. 3600 IN NSEC b.*.t.example.com. A NSEC RRSIG
-;; Used in NXDOMAIN proof test. We are going to test some unusual case where
-;; the best possible wildcard is below the "next domain" of the NSEC RR that
-;; proves the NXDOMAIN, i.e.,
-;; mx.example.com. (exist)
-;; (.no.example.com. (qname, NXDOMAIN)
-;; ).no.example.com. (exist)
-;; *.no.example.com. (best possible wildcard, not exist)
-\).no.example.com. 3600 IN AAAA 2001:db8::53
-;; NSEC records.
-example.com. 3600 IN NSEC cname.example.com. NS SOA NSEC RRSIG
-mx.example.com. 3600 IN NSEC \).no.example.com. MX NSEC RRSIG
-\).no.example.com. 3600 IN NSEC nz.no.example.com. AAAA NSEC RRSIG
-;; We'll also test the case where a single NSEC proves both NXDOMAIN and the
-;; non existence of wildcard. The following records will be used for that
-;; test.
-;; ).no.example.com. (exist, whose NSEC proves everything)
-;; *.no.example.com. (best possible wildcard, not exist)
-;; nx.no.example.com. (NXDOMAIN)
-;; nz.no.example.com. (exist)
-nz.no.example.com. 3600 IN AAAA 2001:db8::5300
-nz.no.example.com. 3600 IN NSEC noglue.example.com. AAAA NSEC RRSIG
-noglue.example.com. 3600 IN NSEC nonsec.example.com. A
-
-;; NSEC for the normal NXRRSET case
-www.example.com. 3600 IN NSEC example.com. A NSEC RRSIG
-
-;; Authoritative data without NSEC
-nonsec.example.com. 3600 IN A 192.0.2.0
-
-;; NSEC3 RRs. You may also need to add mapping to MockZoneFinder::hash_map_.
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 2t7b4g4vsa5smi47k61mv5bv1a22bojr NS SOA NSEC3PARAM RRSIG
-0p9mhaveqvm6t7vbl5lop2u3t2rp3tom.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-q04jkcevqvmu85r014c7dkba38o0ji5r.example.com. 3600 IN RRSIG NSEC3 5 3 3600 20000101000000 20000201000000 12345 example.com. FAKEFAKEFAKE
-
-;; NSEC3 for wild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-ji6neoaepv8b5o6k4ev33abha8ht9fgc.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for cnamewild.example.com (used in wildcard tests, will be added on
-;; demand not to confuse other tests)
-k8udemvp1j2f7eg6jebps17vp3n8i58h.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en
-
-;; NSEC3 for *.uwild.example.com (will be added on demand not to confuse
-;; other tests)
-b4um86eghhds6nea196smvmlo4ors995.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-;; NSEC3 for uwild.example.com. (will be added on demand)
-t644ebqk9bibcna874givr6joj62mlhv.example.com. 3600 IN NSEC3 1 1 12 aabbccdd r53bq7cc2uvmubfu5ocmm6pers9tk9en A RRSIG
-
-;; (Secure) delegation data; Delegation with DS record
-signed-delegation.example.com. 3600 IN NS ns.example.net.
-signed-delegation.example.com. 3600 IN DS 12345 8 2 764501411DE58E8618945054A3F620B36202E115D015A7773F4B78E0F952CECA
-
-;; (Secure) delegation data; Delegation without DS record (and both NSEC
-;; and NSEC3 denying its existence)
-unsigned-delegation.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation.example.com. 3600 IN NSEC unsigned-delegation-optout.example.com. NS RRSIG NSEC
-;; This one will be added on demand
-q81r598950igr1eqvc60aedlq66425b5.example.com. 3600 IN NSEC3 1 1 12 aabbccdd 0p9mhaveqvm6t7vbl5lop2u3t2rp3tom NS RRSIG
-
-;; Delegation without DS record, and no direct matching NSEC3 record
-unsigned-delegation-optout.example.com. 3600 IN NS ns.example.net.
-unsigned-delegation-optout.example.com. 3600 IN NSEC *.uwild.example.com. NS RRSIG NSEC
-
-;; (Secure) delegation data; Delegation where the DS lookup will raise an
-;; exception.
-bad-delegation.example.com. 3600 IN NS ns.example.net.
-
-;; Delegation from an unsigned parent. There's no DS, and there's no NSEC
-;; or NSEC3 that proves it.
-nosec-delegation.example.com. 3600 IN NS ns.nosec.example.net.
diff --git a/src/bin/auth/tests/testdata/example.zone.in b/src/bin/auth/tests/testdata/example.zone.in
new file mode 100644
index 0000000..608c09f
--- /dev/null
+++ b/src/bin/auth/tests/testdata/example.zone.in
@@ -0,0 +1,6 @@
+;;
+;; This is a complete (but crafted and somewhat broken) zone file used
+;; in query tests, excluding NSEC3 records.
+;;
+
+$INCLUDE @abs_builddir@/example-base.zone
diff --git a/src/bin/bind10/bind10_messages.mes b/src/bin/bind10/bind10_messages.mes
index aba8da5..9414ed6 100644
--- a/src/bin/bind10/bind10_messages.mes
+++ b/src/bin/bind10/bind10_messages.mes
@@ -154,7 +154,7 @@ The boss module received the given signal.
% BIND10_RESTART_COMPONENT_SKIPPED Skipped restarting a component %1
The boss module tried to restart a component after it failed (crashed)
unexpectedly, but the boss then found that the component had been removed
-from its local configuration of components to run. This is an unusal
+from its local configuration of components to run. This is an unusual
situation but can happen if the administrator removes the component from
the configuration after the component's crash and before the restart time.
The boss module simply skipped restarting that module, and the whole system
@@ -262,7 +262,7 @@ indicated OS API function with given error.
The boss forwards a request for a socket to the socket creator.
% BIND10_STARTED_CC started configuration/command session
-Debug message given when BIND 10 has successfull started the object that
+Debug message given when BIND 10 has successfully started the object that
handles configuration and commands.
% BIND10_STARTED_PROCESS started %1
@@ -308,13 +308,6 @@ During the startup process, a number of messages are exchanged between the
Boss process and the processes it starts. This error is output when a
message received by the Boss process is not recognised.
-% BIND10_START_AS_NON_ROOT_RESOLVER starting b10-resolver as a user, not root. This might fail.
-The resolver is being started or restarted without root privileges.
-If the module needs these privileges, it may have problems starting.
-Note that this issue should be resolved by the pending 'socket-creator'
-process; once that has been implemented, modules should not need root
-privileges anymore. See tickets #800 and #801 for more information.
-
% BIND10_STOP_PROCESS asking %1 to shut down
The boss module is sending a shutdown command to the given module over
the message channel.
diff --git a/src/bin/bind10/bind10_src.py.in b/src/bin/bind10/bind10_src.py.in
index 882653b..9f41804 100755
--- a/src/bin/bind10/bind10_src.py.in
+++ b/src/bin/bind10/bind10_src.py.in
@@ -103,8 +103,31 @@ VERSION = "bind10 20110223 (BIND 10 @PACKAGE_VERSION@)"
# This is for boot_time of Boss
_BASETIME = time.gmtime()
+# Detailed error message commonly used on startup failure, possibly due to
+# permission issue regarding log lock file. We dump verbose message because
+# it may not be clear exactly what to do if it simply says
+# "failed to open <filename>: permission denied"
+NOTE_ON_LOCK_FILE = """\
+TIP: if this is about permission error for a lock file, check if the directory
+of the file is writable for the user of the bind10 process; often you need
+to start bind10 as a super user. Also, if you specify the -u option to
+change the user and group, the directory must be writable for the group,
+and the created lock file must be writable for that user. Finally, make sure
+the lock file is not left in the directly before restarting.
+"""
+
class ProcessInfoError(Exception): pass
+class ChangeUserError(Exception):
+ '''Exception raised when setuid/setgid fails.
+
+ When raised, it's expected to be propagated via underlying component
+ management modules to the top level so that it will help provide useful
+ fatal error message.
+
+ '''
+ pass
+
class ProcessInfo:
"""Information about a process"""
@@ -206,8 +229,8 @@ class BoB:
# restart. Components manage their own restart schedule now
self.components_to_restart = []
self.runnable = False
- self.uid = setuid
- self.gid = setgid
+ self.__uid = setuid
+ self.__gid = setgid
self.username = username
self.verbose = verbose
self.nokill = nokill
@@ -269,6 +292,31 @@ class BoB:
# Update the configuration
self._component_configurator.reconfigure(comps)
+ def change_user(self):
+ '''Change the user and group to those specified on construction.
+
+ This method is expected to be called by a component on initial
+ startup when the system is ready to switch the user and group
+ (i.e., once all components that need the privilege of the original
+ user have started).
+ '''
+ try:
+ if self.__gid is not None:
+ logger.info(BIND10_SETGID, self.__gid)
+ posix.setgid(self.__gid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change group: ' + str(ex))
+
+ try:
+ if self.__uid is not None:
+ posix.setuid(self.__uid)
+ # We use one-shot logger after setuid here. This will
+ # detect any permission issue regarding logging due to the
+ # result of setuid at the earliest opportunity.
+ isc.log.Logger("boss").info(BIND10_SETUID, self.__uid)
+ except Exception as ex:
+ raise ChangeUserError('failed to change user: ' + str(ex))
+
def config_handler(self, new_config):
# If this is initial update, don't do anything now, leave it to startup
if not self.runnable:
@@ -443,6 +491,8 @@ class BoB:
# if we have been trying for "a while" give up
if (time.time() - cc_connect_start) > self.msgq_timeout:
+ if msgq_proc.process:
+ msgq_proc.process.kill()
logger.error(BIND10_CONNECTING_TO_CC_FAIL)
raise CChannelConnectError("Unable to connect to c-channel after 5 seconds")
@@ -578,8 +628,6 @@ class BoB:
are pure speculation. As with the auth daemon, they should be
read from the configuration database.
"""
- if self.uid is not None and self.__started:
- logger.warn(BIND10_START_AS_NON_ROOT_RESOLVER)
self.curproc = "b10-resolver"
# XXX: this must be read from the configuration manager in the future
resargs = ['b10-resolver']
@@ -646,6 +694,9 @@ class BoB:
try:
self.c_channel_env = c_channel_env
self.start_all_components()
+ except ChangeUserError as e:
+ self.kill_started_components()
+ return str(e) + '; ' + NOTE_ON_LOCK_FILE.replace('\n', ' ')
except Exception as e:
self.kill_started_components()
return "Unable to start " + self.curproc + ": " + str(e)
@@ -1155,7 +1206,19 @@ def remove_lock_files():
for f in lockfiles:
fname = lpath + '/' + f
if os.path.isfile(fname):
- os.unlink(fname)
+ try:
+ os.unlink(fname)
+ except OSError as e:
+ # We catch and ignore permission related error on unlink.
+ # This can happen if bind10 started with -u, created a lock
+ # file as a privileged user, but the directory is not writable
+ # for the changed user. This setup will cause immediate
+ # start failure, and we leave verbose error message including
+ # the leftover lock file, so it should be acceptable to ignore
+ # it (note that it doesn't make sense to log this event at
+ # this poitn)
+ if e.errno != errno.EPERM and e.errno != errno.EACCES:
+ raise
return
@@ -1173,13 +1236,7 @@ def main():
except RuntimeError as e:
sys.stderr.write('ERROR: failed to write the initial log: %s\n' %
str(e))
- sys.stderr.write("""\
-TIP: if this is about permission error for a lock file, check if the directory
-of the file is writable for the user of the bind10 process; often you need
-to start bind10 as a super user. Also, if you specify the -u option to
-change the user and group, the directory must be writable for the group,
-and the created lock file must be writable for that user.
-""")
+ sys.stderr.write(NOTE_ON_LOCK_FILE)
sys.exit(1)
# Check user ID.
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 409790c..ccfa831 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -341,6 +341,18 @@ class TestCacheCommands(unittest.TestCase):
class TestBoB(unittest.TestCase):
+ def setUp(self):
+ # Save original values that may be tweaked in some tests
+ self.__orig_setgid = bind10_src.posix.setgid
+ self.__orig_setuid = bind10_src.posix.setuid
+ self.__orig_logger_class = isc.log.Logger
+
+ def tearDown(self):
+ # Restore original values saved in setUp()
+ bind10_src.posix.setgid = self.__orig_setgid
+ bind10_src.posix.setuid = self.__orig_setuid
+ isc.log.Logger = self.__orig_logger_class
+
def test_init(self):
bob = BoB()
self.assertEqual(bob.verbose, False)
@@ -349,10 +361,56 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
self.assertIsNone(bob._socket_cache)
+ def __setgid(self, gid):
+ self.__gid_set = gid
+
+ def __setuid(self, uid):
+ self.__uid_set = uid
+
+ def test_change_user(self):
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+
+ self.__gid_set = None
+ self.__uid_set = None
+ bob = BoB()
+ bob.change_user()
+ # No gid/uid set in boss, nothing called.
+ self.assertIsNone(self.__gid_set)
+ self.assertIsNone(self.__uid_set)
+
+ BoB(setuid=42, setgid=4200).change_user()
+ # This time, it get's called
+ self.assertEqual(4200, self.__gid_set)
+ self.assertEqual(42, self.__uid_set)
+
+ def raising_set_xid(gid_or_uid):
+ ex = OSError()
+ ex.errno, ex.strerror = errno.EPERM, 'Operation not permitted'
+ raise ex
+
+ # Let setgid raise an exception
+ bind10_src.posix.setgid = raising_set_xid
+ bind10_src.posix.setuid = self.__setuid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
+ # Let initial log output after setuid raise an exception
+ bind10_src.posix.setgid = self.__setgid
+ bind10_src.posix.setuid = self.__setuid
+ isc.log.Logger = raising_set_xid
+ self.assertRaises(bind10_src.ChangeUserError,
+ BoB(setuid=42, setgid=4200).change_user)
+
def test_set_creator(self):
"""
Test the call to set_creator. First time, the cache is created
@@ -423,7 +481,6 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.ccs, None)
self.assertEqual(bob.components, {})
self.assertEqual(bob.runnable, False)
- self.assertEqual(bob.uid, None)
self.assertEqual(bob.username, None)
def test_command_handler(self):
@@ -2021,8 +2078,10 @@ class TestBossComponents(unittest.TestCase):
def start_all_components(self):
self.started = True
- if self.throw:
+ if self.throw is True:
raise Exception('Assume starting components has failed.')
+ elif self.throw:
+ raise self.throw
def kill_started_components(self):
self.killed = True
@@ -2067,6 +2126,12 @@ class TestBossComponents(unittest.TestCase):
r = bob.startup()
self.assertEqual({'BIND10_MSGQ_SOCKET_FILE': 'foo'}, bob.c_channel_env)
+ # Check failure of changing user results in a different message
+ bob = MockBobStartup(bind10_src.ChangeUserError('failed to chusr'))
+ r = bob.startup()
+ self.assertIn('failed to chusr', r)
+ self.assertTrue(bob.killed)
+
# Check the case when socket file already exists
isc.cc.Session = DummySessionSocketExists
bob = MockBobStartup(False)
@@ -2277,11 +2342,15 @@ class TestFunctions(unittest.TestCase):
self.assertFalse(os.path.exists(self.lockfile_testpath))
os.mkdir(self.lockfile_testpath)
self.assertTrue(os.path.isdir(self.lockfile_testpath))
+ self.__isfile_orig = bind10_src.os.path.isfile
+ self.__unlink_orig = bind10_src.os.unlink
def tearDown(self):
os.rmdir(self.lockfile_testpath)
self.assertFalse(os.path.isdir(self.lockfile_testpath))
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = "@abs_top_builddir@"
+ bind10_src.os.path.isfile = self.__isfile_orig
+ bind10_src.os.unlink = self.__unlink_orig
def test_remove_lock_files(self):
os.environ["B10_LOCKFILE_DIR_FROM_BUILD"] = self.lockfile_testpath
@@ -2305,6 +2374,28 @@ class TestFunctions(unittest.TestCase):
# second call should not assert anyway
bind10_src.remove_lock_files()
+ def test_remove_lock_files_fail(self):
+ # Permission error on unlink is ignored; other exceptions are really
+ # unexpected and propagated.
+ def __raising_unlink(unused, ex):
+ raise ex
+
+ bind10_src.os.path.isfile = lambda _: True
+ os_error = OSError()
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, os_error)
+
+ os_error.errno = errno.EPERM
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.EACCES
+ bind10_src.remove_lock_files() # no disruption
+
+ os_error.errno = errno.ENOENT
+ self.assertRaises(OSError, bind10_src.remove_lock_files)
+
+ bind10_src.os.unlink = lambda f: __raising_unlink(f, Exception('bad'))
+ self.assertRaises(Exception, bind10_src.remove_lock_files)
+
def test_get_signame(self):
# just test with some samples
signame = bind10_src.get_signame(signal.SIGTERM)
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 37b74e7..7c2b2af 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -208,12 +208,13 @@ WARNING: Python readline module isn't available, so the command line editor
return True
def login_to_cmdctl(self):
- '''Login to cmdctl with the username and password inputted
- from user. After the login is sucessful, the username and
+ '''Login to cmdctl with the username and password given by
+ the user. After the login is sucessful, the username and
password will be saved in 'default_user.csv', when run the next
time, username and password saved in 'default_user.csv' will be
used first.
'''
+ # Look at existing username/password combinations and try to log in
users = self._get_saved_user_info(self.csv_file_dir, CSV_FILE_NAME)
for row in users:
param = {'username': row[0], 'password' : row[1]}
@@ -230,15 +231,18 @@ WARNING: Python readline module isn't available, so the command line editor
print(data + ' login as ' + row[0])
return True
+ # No valid logins were found, prompt the user for a username/password
count = 0
- print("[TEMP MESSAGE]: username :root password :bind10")
+ print('No stored password file found, please see sections '
+ '"Configuration specification for b10-cmdctl" and "bindctl '
+ 'command-line options" of the BIND 10 guide.')
while True:
count = count + 1
if count > 3:
print("Too many authentication failures")
return False
- username = input("Username:")
+ username = input("Username: ")
passwd = getpass.getpass()
param = {'username': username, 'password' : passwd}
try:
diff --git a/src/bin/bindctl/run_bindctl.sh.in b/src/bin/bindctl/run_bindctl.sh.in
index f4cc40c..999d7ee 100755
--- a/src/bin/bindctl/run_bindctl.sh.in
+++ b/src/bin/bindctl/run_bindctl.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
BINDCTL_PATH=@abs_top_builddir@/src/bin/bindctl
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/dbutil/dbutil_messages.mes b/src/bin/dbutil/dbutil_messages.mes
index 90ede92..2fbd000 100644
--- a/src/bin/dbutil/dbutil_messages.mes
+++ b/src/bin/dbutil/dbutil_messages.mes
@@ -53,7 +53,7 @@ inconsistent state, and it is advised to restore it from the backup that was
created when b10-dbutil started.
% DBUTIL_EXECUTE Executing SQL statement: %1
-Debug message; the given SQL statement is executed
+Debug message; the given SQL statement is executed.
% DBUTIL_FILE Database file: %1
The database file that is being checked.
@@ -67,7 +67,7 @@ The given database statement failed to execute. The error is shown in the
message.
% DBUTIL_TOO_MANY_ARGUMENTS too many arguments to the command, maximum of one expected
-There were too many command-line arguments to b10-dbutil
+There were too many command-line arguments to b10-dbutil.
% DBUTIL_UPGRADE_CANCELED upgrade canceled; database has not been changed
The user aborted the upgrade, and b10-dbutil will now exit.
@@ -95,7 +95,7 @@ again.
% DBUTIL_UPGRADE_PREPARATION_FAILED upgrade preparation failed: %1
An unexpected error occurred while b10-dbutil was preparing to upgrade the
-database schema. The error is shown in the message
+database schema. The error is shown in the message.
% DBUTIL_UPGRADE_SUCCESFUL database upgrade successfully completed
The database schema update was completed successfully.
diff --git a/src/bin/dbutil/run_dbutil.sh.in b/src/bin/dbutil/run_dbutil.sh.in
index fea7482..f0c6dbd 100755
--- a/src/bin/dbutil/run_dbutil.sh.in
+++ b/src/bin/dbutil/run_dbutil.sh.in
@@ -20,7 +20,10 @@ export PYTHON_EXEC
DBUTIL_PATH=@abs_top_builddir@/src/bin/dbutil
-PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python
+# Note: lib/dns/python/.libs is necessary because __init__.py of isc package
+# automatically imports isc.datasrc, which then requires the DNS loadable
+# module. #2145 should eliminate the need for it.
+PYTHONPATH=@abs_top_srcdir@/src/bin:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/bin:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/dhcp4/config_parser.cc b/src/bin/dhcp4/config_parser.cc
index 76181c2..fdb4240 100644
--- a/src/bin/dhcp4/config_parser.cc
+++ b/src/bin/dhcp4/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,7 +18,10 @@
#include <dhcp/libdhcp++.h>
#include <dhcp/option_definition.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
+#include <dhcpsrv/option_space_container.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/foreach.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
@@ -28,31 +31,56 @@
#include <map>
using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
+
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
/// @brief auxiliary type used for storing element name and its parser
typedef pair<string, ConstElementPtr> ConfigPair;
/// @brief a factory method that will create a parser for a given element name
-typedef Dhcp4ConfigParser* ParserFactory(const std::string& config_id);
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
/// @brief a collection of factories that creates parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
+/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+typedef std::map<std::string, uint32_t> Uint32Storage;
+
+/// @brief a collection of elements that store string values
+typedef std::map<std::string, std::string> StringStorage;
+
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
/// @brief a collection of pools
///
/// That type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
typedef std::vector<Pool4Ptr> PoolStorage;
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -63,18 +91,21 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
/// @brief a dummy configuration parser
///
/// It is a debugging parser. It does not configure anything,
/// will accept any configuration and will just print it out
/// on commit. Useful for debugging existing configurations and
/// adding new ones.
-class DebugParser : public Dhcp4ConfigParser {
+class DebugParser : public DhcpConfigParser {
public:
/// @brief Constructor
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -83,7 +114,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref Dhcp4ConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -97,7 +128,7 @@ public:
/// 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.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -109,7 +140,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* Factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -121,6 +152,85 @@ private:
ConstElementPtr value_;
};
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -128,27 +238,36 @@ private:
/// (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.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class Uint32Parser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
/// @throw BadValue if supplied value could not be base to uint32_t
+ /// or the parameter name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
int64_t check;
string x = value->str();
try {
@@ -168,31 +287,27 @@ public:
// 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.
+ /// @brief Stores the parsed uint32_t value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs Uint32Parser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv4ConfigInherit for details.
+ /// See @ref dhcpv4ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(Uint32Storage* storage) {
@@ -217,47 +332,48 @@ private:
/// (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.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv4ConfigInherit page.
-class StringParser : public Dhcp4ConfigParser {
+/// @ref dhcpv4ConfigInherit page.
+class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
:storage_(&string_defaults), param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
/// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// @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.
+ /// @brief Stores the parsed value in a storage.
virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
}
/// @brief factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
@@ -290,7 +406,7 @@ private:
/// designates all interfaces.
///
/// It is useful for parsing Dhcp4/interface parameter.
-class InterfaceListConfigParser : public Dhcp4ConfigParser {
+class InterfaceListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -328,7 +444,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -347,7 +463,7 @@ private:
/// before build(). Otherwise exception will be thrown.
///
/// It is useful for parsing Dhcp4/subnet4[X]/pool parameters.
-class PoolParser : public Dhcp4ConfigParser {
+class PoolParser : public DhcpConfigParser {
public:
/// @brief constructor.
@@ -363,7 +479,7 @@ public:
/// 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
+ /// @throw DhcpConfigError when pool parsing fails
void build(ConstElementPtr pools_list) {
// setStorage() should have been called before build
if (!pools_) {
@@ -403,12 +519,12 @@ public:
// be checked in Pool4 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp4ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "Failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool4Ptr pool(new Pool4(addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -421,11 +537,11 @@ public:
Pool4Ptr pool(new Pool4(min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp4ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "Failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -440,17 +556,22 @@ public:
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -460,22 +581,30 @@ private:
/// That is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
/// @brief Parser for option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful then an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere until support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
-class OptionDataParser : public Dhcp4ConfigParser {
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
+class OptionDataParser : public DhcpConfigParser {
public:
/// @brief Constructor.
@@ -498,11 +627,10 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp4ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError if invalid parameter specified in
/// the configuration.
/// @throw isc::InvalidOperation if failed to set storage prior to
/// calling build.
- /// @throw isc::BadValue if option data storage is invalid.
virtual void build(ConstElementPtr option_data_entries) {
if (options_ == NULL) {
isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
@@ -510,33 +638,41 @@ public:
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
- } else if (param.first == "data") {
- boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
if (value_parser) {
- value_parser->setStorage(&string_values_);
+ value_parser->setStorage(&boolean_values_);
parser = value_parser;
}
} else {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -552,16 +688,21 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
"commiting option data.");
} else if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is not
// than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
@@ -571,7 +712,7 @@ public:
idx.erase(range.first, range.second);
}
// Append new option to the main storage.
- options_->push_back(option_descriptor_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -599,63 +740,102 @@ private:
/// is intitialized but this check is not needed here because it is done
/// in the \ref build function.
///
- /// @throw Dhcp4ConfigError if parameters provided in the configuration
+ /// @throw DhcpConfigError if parameters provided in the configuration
/// are invalid.
void createOption() {
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
// does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getUint32Param("code");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv4.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(Dhcp4ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " exceed " << std::numeric_limits<uint16_t>::max());
}
// Check that the option name has been specified, is non-empty and does not
// contain spaces.
// @todo possibly some more restrictions apply here?
- std::string option_name = getStringParam("name");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp4ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
<< " empty");
} else if (option_name.find(" ") != std::string::npos) {
- isc_throw(Dhcp4ConfigError, "Parser error: option name must not contain"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V4, option_code);
+
+ } else if (option_space == "dhcp6") {
+ isc_throw(DhcpConfigError, "'dhcp6' option space name is reserved"
+ << " for DHCPv6 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp4ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
- // Get all existing DHCPv4 option definitions. The one that matches
- // our option will be picked and used to create it.
- OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V4);
- // Get search index #1. It allows searching for options definitions
- // using option type value.
- const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
- // Get all option definitions matching option code we want to create.
- const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
- size_t num_defs = std::distance(range.first, range.second);
+
OptionPtr option;
- // Currently we do not allow duplicated definitions and if there are
- // any duplicates we issue internal server error.
- if (num_defs > 1) {
- isc_throw(Dhcp4ConfigError, "Internal error: currently it is not"
- << " supported to initialize multiple option definitions"
- << " for the same option code. This will be supported once"
- << " there option spaces are implemented.");
- } else if (num_defs == 0) {
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -669,57 +849,52 @@ private:
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} else {
- // We have exactly one option definition for the particular option code
- // use it to create the option instance.
- const OptionDefinitionPtr& def = *(range.first);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
- OptionPtr option = def->optionFactory(Option::V4, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V4, option_code, data_tokens) :
+ def->optionFactory(Option::V4, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
- isc_throw(Dhcp4ConfigError, "Parser error: option data does not match"
- << " option definition (code " << option_code << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
<< ex.what());
}
}
- }
-
- /// @brief Get a parameter from the strings storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp4ConfigError if parameter has not been found.
- std::string getStringParam(const std::string& param_id) const {
- StringStorage::const_iterator param = string_values_.find(param_id);
- if (param == string_values_.end()) {
- isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
- }
-
- /// @brief Get a parameter from the uint32 values storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp4ConfigError if parameter has not been found.
- uint32_t getUint32Param(const std::string& param_id) const {
- Uint32Storage::const_iterator param = uint32_values_.find(param_id);
- if (param == uint32_values_.end()) {
- isc_throw(Dhcp4ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
/// Option descriptor holds newly configured option.
Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -728,7 +903,7 @@ private:
/// data for a particular subnet and creates a collection of options.
/// If parsing is successful, all these options are added to the Subnet
/// object.
-class OptionDataListParser : public Dhcp4ConfigParser {
+class OptionDataListParser : public DhcpConfigParser {
public:
/// @brief Constructor.
@@ -745,7 +920,7 @@ public:
/// for options within a single subnet and creates options' instances.
///
/// @param option_data_list pointer to a list of options' data sets.
- /// @throw Dhcp4ConfigError if option parsing failed.
+ /// @throw DhcpConfigError if option parsing failed.
void build(ConstElementPtr option_data_list) {
BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -785,7 +960,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -800,11 +975,259 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in a storage.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
/// for received configuration parameters as needed.
-class Subnet4ConfigParser : public Dhcp4ConfigParser {
+class Subnet4ConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -841,10 +1264,23 @@ public:
// Appropriate parsers are created in the createSubnet6ConfigParser
// and they should be limited to those that we check here for. Thus,
// if we fail to find a matching parser here it is a programming error.
- isc_throw(Dhcp4ConfigError, "failed to find suitable parser");
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
+ BOOST_FOREACH(ParserPtr parser, parsers_) {
+ parser->commit();
+ }
+
+ // Create a subnet.
+ createSubnet();
}
/// @brief commits received configuration.
@@ -853,30 +1289,84 @@ public:
/// 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
+ /// @throw DhcpConfigError if there are any issues encountered during commit
void commit() {
- // Invoke commit on all sub-data parsers.
- BOOST_FOREACH(ParserPtr parser, parsers_) {
- parser->commit();
+ if (subnet_) {
+ CfgMgr::instance().addSubnet4(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
}
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp4ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> valid = getParam("valid-lifetime");
@@ -888,83 +1378,66 @@ public:
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet4Ptr subnet(new Subnet4(addr, len, t1, t2, valid));
+ subnet_.reset(new Subnet4(addr, len, t1, t2, valid));
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool4(*it);
+ subnet_->addPool(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
-
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp4_logger, DHCP4_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
-
- CfgMgr::instance().addSubnet4(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
@@ -974,15 +1447,15 @@ 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
- Dhcp4ConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
+ DhcpConfigParser* createSubnet4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["subnet"] = StringParser::Factory;
- factories["pool"] = PoolParser::Factory;
- factories["option-data"] = OptionDataListParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -990,13 +1463,13 @@ private:
// return new DebugParser(config_id);
isc_throw(NotImplemented,
- "Parser error: Subnet4 parameter not supported: "
+ "parser error: Subnet4 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1004,7 +1477,7 @@ private:
///
/// @param name name of the parameter
/// @return triplet with the parameter name
- /// @throw Dhcp4ConfigError when requested parameter is not present
+ /// @throw DhcpConfigError when requested parameter is not present
Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
@@ -1023,7 +1496,7 @@ private:
if (found) {
return (Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp4ConfigError, "Mandatory parameter " << name
+ isc_throw(DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1043,6 +1516,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// @brief Pointer to the created subnet object.
+ isc::dhcp::Subnet4Ptr subnet_;
};
/// @brief this class parses list of subnets
@@ -1050,7 +1526,7 @@ private:
/// 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 {
+class Subnets4ListConfigParser : public DhcpConfigParser {
public:
/// @brief constructor
@@ -1098,14 +1574,20 @@ public:
/// @brief Returns Subnet4ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets4ListConfigParser object
- static Dhcp4ConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets4ListConfigParser(param_name));
}
/// @brief collection of subnet parsers.
ParserCollection subnets_;
+
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1114,16 +1596,17 @@ public:
/// @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) {
+DhcpConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories["valid-lifetime"] = Uint32Parser::Factory;
- factories["renew-timer"] = Uint32Parser::Factory;
- factories["rebind-timer"] = Uint32Parser::Factory;
- factories["interface"] = InterfaceListConfigParser::Factory;
- factories["subnet4"] = Subnets4ListConfigParser::Factory;
- factories["option-data"] = OptionDataListParser::Factory;
- factories["version"] = StringParser::Factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet4"] = Subnets4ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1138,7 +1621,7 @@ Dhcp4ConfigParser* createGlobalDhcp4ConfigParser(const std::string& config_id) {
}
isc::data::ConstElementPtr
-configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
+configureDhcp4Server(Dhcpv4Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -1150,44 +1633,133 @@ configureDhcp4Server(Dhcpv4Srv& , ConstElementPtr config_set) {
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()) {
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
+ try {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
ParserPtr parser(createGlobalDhcp4ConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ if (config_pair.first == "subnet4") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
}
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet4");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
+ }
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed: ") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed: ") + ex.what());
+ rollback = true;
+
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ rollback = true;
+
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed: ") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
+const std::map<std::string, uint32_t>& getUint32Defaults() {
+ return (uint32_defaults);
+}
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/config_parser.h b/src/bin/dhcp4/config_parser.h
index cc4c690..4f1ea32 100644
--- a/src/bin/dhcp4/config_parser.h
+++ b/src/bin/dhcp4/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -28,117 +28,12 @@ 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
+/// @brief Configure DHCPv4 server (@c Dhcpv4Srv) with a set of configuration values.
///
-/// 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.
+/// 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,
+/// 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.
///
@@ -154,7 +49,8 @@ typedef std::vector<ParserPtr> ParserCollection;
/// 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)
+/// 2 - commit failed (parsing was successful, but failed to store the
+/// values in to server's configuration)
///
/// @param config_set a new configuration (JSON) for DHCPv4 server
/// @return answer that contains result of reconfiguration
@@ -162,6 +58,16 @@ isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
+
+/// @brief Returns the global uint32_t values storage.
+///
+/// This function must be only used by unit tests that need
+/// to access uint32_t global storage to verify that the
+/// Uint32Parser works as expected.
+///
+/// @return a reference to a global uint32 values storage.
+const std::map<std::string, uint32_t>& getUint32Defaults();
+
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index eb5d482..435a25e 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/spec_config.h>
@@ -121,7 +122,7 @@ void ControlledDhcpv4Srv::establishSession() {
try {
configureDhcp4Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp4ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp4/dhcp4.spec b/src/bin/dhcp4/dhcp4.spec
index 46192de..c2b755c 100644
--- a/src/bin/dhcp4/dhcp4.spec
+++ b/src/bin/dhcp4/dhcp4.spec
@@ -34,6 +34,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -61,6 +111,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
} ]
}
},
@@ -141,6 +201,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp4"
} ]
}
} ]
diff --git a/src/bin/dhcp4/dhcp4_messages.mes b/src/bin/dhcp4/dhcp4_messages.mes
index c7bfeae..02ad0a0 100644
--- a/src/bin/dhcp4/dhcp4_messages.mes
+++ b/src/bin/dhcp4/dhcp4_messages.mes
@@ -26,12 +26,6 @@ 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_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_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
@@ -41,19 +35,52 @@ served until the configuration has been corrected.
This is an informational message reporting that the configuration has
been extended to include the specified IPv4 subnet.
+% DHCP4_CONFIG_OPTION_DUPLICATE multiple options with the code %1 added to the subnet %2
+This warning message is issued on an attempt to configure multiple options
+with the same option code for a particular subnet. Adding multiple options
+is uncommon for DHCPv4, but is not prohibited.
+
+% DHCP4_CONFIG_UPDATE updated configuration received: %1
+A debug message indicating that the IPv4 DHCP server has received an
+updated configuration from the BIND 10 configuration system.
+
% DHCP4_CONFIG_START DHCPv4 server is processing the following configuration: %1
This is a debug message that is issued every time the server receives a
configuration. That happens at start up and also when a server configuration
change is committed by the administrator.
-% DHCP4_CONFIG_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_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_CONFIG_OPTION_DUPLICATE multiple options with the code: %1 added to the subnet: %2
-This warning message is issued on an attempt to configure multiple options with the
-same option code for the particular subnet. Adding multiple options is uncommon
-for DHCPv4, but it is not prohibited.
+% DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
+This informational message is printed every time DHCPv4 server is started
+and gives both the type and name of the database being used to store
+lease and other information.
+
+% DHCP4_LEASE_ADVERT lease %1 advertised (client client-id %2, hwaddr %3)
+This debug message indicates that the server successfully advertised
+a lease. It is up to the client to choose one server out of othe advertised
+and continue allocation with that server. This is a normal behavior and
+indicates successful operation.
+
+% DHCP4_LEASE_ADVERT_FAIL failed to advertise a lease for client client-id %1, hwaddr %2
+This message indicates that the server has failed to offer a lease to
+the specified client after receiving a DISCOVER message from it. There are
+many possible reasons for such a failure.
+
+% DHCP4_LEASE_ALLOC lease %1 has been allocated for client-id %2, hwaddr %3
+This debug message indicates that the server successfully granted a lease
+in response to client's REQUEST message. This is a normal behavior and
+incicates successful operation.
+
+% DHCP4_LEASE_ALLOC_FAIL failed to grant a lease for client-id %1, hwaddr %2
+This message indicates that the server failed to grant a lease to the
+specified client after receiving a REQUEST message from it. There are many
+possible reasons for such a failure. Additional messages will indicate the
+reason.
% DHCP4_NOT_RUNNING IPv4 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
@@ -75,7 +102,7 @@ may well be a valid DHCP packet, just a type not expected by the server
% DHCP4_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv4 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP4_PACKET_SEND_FAIL failed to send DHCPv4 packet: %1
@@ -91,6 +118,43 @@ to be a programming error: please raise a bug report.
% DHCP4_QUERY_DATA received packet type %1, data is <%2>
A debug message listing the data received from the client.
+% DHCP4_RELEASE address %1 belonging to client-id %2, hwaddr %3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP4_RELEASE_EXCEPTION exception %1 while trying to release address %2
+This message is output when an error was encountered during an attempt
+to process a RELEASE message. The error will not affect the client,
+which does not expect any response from the server for RELEASE
+messages. Depending on the nature of problem, it may affect future
+server operation.
+
+% DHCP4_RELEASE_FAIL failed to remove lease for address %1 for duid %2, hwaddr %3
+This error message indicates that the software failed to remove a
+lease from the lease database. It is probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP4_RELEASE_FAIL_NO_LEASE client (client-id %2) tried to release address %1, but there is no lease for such address.
+This warning message is printed when client attempts to release a lease,
+but no such lease is known to the server.
+
+% DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID client (client-id %2) tried to release address %1, but it belongs to client (client-id %3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP4_RELEASE_FAIL_WRONG_HWADDR client (client-id %2) tried to release address %1, but sent from a wrong hardware address (%3)
+This warning message indicates that client tried to release an address
+that does belong to it, but the lease information was associated with
+a different hardware address. One possible reason for using different
+hardware address is that a cloned virtual machine was not updated and
+both clones use the same client-id.
+
% DHCP4_RESPONSE_DATA responding with packet type %1, data is <%2>
A debug message listing the data returned to the client.
@@ -98,6 +162,26 @@ A debug message listing the data returned to the client.
The IPv4 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
+% DHCP4_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to
+read its server identifier and has generated a new one. This server-id
+will be stored in a file and will be read (and used) whenever the server
+is restarted. This is normal behavior when the server is started for the
+first time. If this message is printed every time the server is started,
+please check that the server has sufficient permission to write its
+server-id file and that the file is not corrupt.
+
+% DHCP4_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP4_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier to a file. The most likely cause is is that the server
+does not have permissions to write the server id file.
+
% DHCP4_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
@@ -131,3 +215,13 @@ processed any command-line switches and is starting.
This is a debug message issued during the IPv4 DHCP server startup.
It lists some information about the parameters with which the server
is running.
+
+% DHCP4_SUBNET_SELECTED the %1 subnet was selected for client assignment
+This is a debug message noting the selection of a subnet to be used for
+address and option assignment. Subnet selection is one of the early
+steps in the processing of incoming client message.
+
+% DHCP4_SUBNET_SELECTION_FAILED failed to select a subnet for incoming packet, src: %1, type: %2
+This warning message is output when a packet was received from a subnet
+for which the DHCPv4 server has not been configured. The most probable
+cause is a misconfiguration of the server.
diff --git a/src/bin/dhcp4/dhcp4_srv.cc b/src/bin/dhcp4/dhcp4_srv.cc
index 59edc01..20d8597 100644
--- a/src/bin/dhcp4/dhcp4_srv.cc
+++ b/src/bin/dhcp4/dhcp4_srv.cc
@@ -16,9 +16,24 @@
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/option4_addrlst.h>
+#include <dhcp/option_int.h>
#include <dhcp/pkt4.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
+#include <dhcpsrv/addr_utilities.h>
+
+#include <boost/algorithm/string/erase.hpp>
+
+#include <iomanip>
+#include <fstream>
using namespace isc;
using namespace isc::asiolink;
@@ -28,25 +43,48 @@ using namespace std;
// These are hardcoded parameters. Currently this is a skeleton server that only
// grants those options and a single, fixed, hardcoded lease.
-const std::string HARDCODED_LEASE = "192.0.2.222"; // assigned lease
-const std::string HARDCODED_NETMASK = "255.255.255.0";
-const uint32_t HARDCODED_LEASE_TIME = 60; // in seconds
const std::string HARDCODED_GATEWAY = "192.0.2.1";
const std::string HARDCODED_DNS_SERVER = "192.0.2.2";
const std::string HARDCODED_DOMAIN_NAME = "isc.example.com";
-const std::string HARDCODED_SERVER_ID = "192.0.2.1";
-Dhcpv4Srv::Dhcpv4Srv(uint16_t port) {
+Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
// First call to instance() will create IfaceMgr (it's a singleton)
// it may throw something if things go wrong
IfaceMgr::instance();
- /// @todo: instantiate LeaseMgr here once it is imlpemented.
- IfaceMgr::instance().openSockets4(port);
+ if (port) {
+ // open sockets only if port is non-zero. Port 0 is used
+ // for non-socket related testing.
+ IfaceMgr::instance().openSockets4(port);
+ }
+
+ string srvid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_ID_FILE);
+ if (loadServerID(srvid_file)) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_SERVERID_LOADED)
+ .arg(srvid_file);
+ } else {
+ generateServerID();
+ LOG_INFO(dhcp4_logger, DHCP4_SERVERID_GENERATED)
+ .arg(srvidToString(getServerID()))
+ .arg(srvid_file);
+
+ if (!writeServerID(srvid_file)) {
+ LOG_WARN(dhcp4_logger, DHCP4_SERVERID_WRITE_FAIL)
+ .arg(srvid_file);
+ }
+
+ }
+
+ // Instantiate LeaseMgr
+ LeaseMgrFactory::create(dbconfig);
+ LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
+ .arg(LeaseMgrFactory::instance().getType())
+ .arg(LeaseMgrFactory::instance().getName());
- setServerID();
+ // Instantiate allocation engine
+ alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100));
} catch (const std::exception &e) {
LOG_ERROR(dhcp4_logger, DHCP4_SRV_CONSTRUCT_ERROR).arg(e.what());
@@ -157,27 +195,109 @@ Dhcpv4Srv::run() {
}
}
}
+ }
+
+ return (true);
+}
- // TODO add support for config session (see src/bin/auth/main.cc)
- // so this daemon can be controlled from bob
+bool Dhcpv4Srv::loadServerID(const std::string& file_name) {
+
+ // load content of the file into a string
+ fstream f(file_name.c_str(), ios::in);
+ if (!f.is_open()) {
+ return (false);
+ }
+
+ string hex_string;
+ f >> hex_string;
+ f.close();
+
+ // remove any spaces
+ boost::algorithm::erase_all(hex_string, " ");
+
+ try {
+ IOAddress addr(hex_string);
+
+ if (!addr.isV4()) {
+ return (false);
+ }
+
+ // Now create server-id option
+ serverid_.reset(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, addr));
+
+ } catch(...) {
+ // any kind of malformed input (empty string, IPv6 address, complete
+ // garbate etc.)
+ return (false);
}
return (true);
}
-void
-Dhcpv4Srv::setServerID() {
- /// TODO implement this for real once interface detection (ticket 1237)
- /// is done. Use hardcoded server-id for now.
-
-#if 0
- // uncomment this once ticket 1350 is merged.
- IOAddress srvId("127.0.0.1");
- serverid_ = OptionPtr(
- new Option4AddrLst(Option::V4, DHO_DHCP_SERVER_IDENTIFIER, srvId));
-#endif
+void Dhcpv4Srv::generateServerID() {
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // Let's find suitable interface.
+ for (IfaceMgr::IfaceCollection::const_iterator iface = ifaces.begin();
+ iface != ifaces.end(); ++iface) {
+
+ // Let's don't use loopback.
+ if (iface->flag_loopback_) {
+ continue;
+ }
+
+ // Let's skip downed interfaces. It is better to use working ones.
+ if (!iface->flag_up_) {
+ continue;
+ }
+
+ const IfaceMgr::AddressCollection addrs = iface->getAddresses();
+
+ for (IfaceMgr::AddressCollection::const_iterator addr = addrs.begin();
+ addr != addrs.end(); ++addr) {
+ if (addr->getFamily() != AF_INET) {
+ continue;
+ }
+
+ serverid_ = OptionPtr(new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER,
+ *addr));
+ return;
+ }
+
+
+ }
+
+ isc_throw(BadValue, "No suitable interfaces for server-identifier found");
+}
+
+bool Dhcpv4Srv::writeServerID(const std::string& file_name) {
+ fstream f(file_name.c_str(), ios::out | ios::trunc);
+ if (!f.good()) {
+ return (false);
+ }
+ f << srvidToString(getServerID());
+ f.close();
}
+string Dhcpv4Srv::srvidToString(const OptionPtr& srvid) {
+ if (!srvid) {
+ isc_throw(BadValue, "NULL pointer passed to srvidToString()");
+ }
+ boost::shared_ptr<Option4AddrLst> generated =
+ boost::dynamic_pointer_cast<Option4AddrLst>(srvid);
+ if (!srvid) {
+ isc_throw(BadValue, "Pointer to invalid option passed to srvidToString()");
+ }
+
+ Option4AddrLst::AddressContainer addrs = generated->getAddresses();
+ if (addrs.size() != 1) {
+ isc_throw(BadValue, "Malformed option passed to srvidToString(). "
+ << "Expected to contain a single IPv4 address.");
+ }
+
+ return (addrs[0].toText());
+}
void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setIface(question->getIface());
@@ -188,9 +308,7 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setHops(question->getHops());
// copy MAC address
- vector<uint8_t> mac(question->getChaddr(),
- question->getChaddr() + Pkt4::MAX_CHADDR_LEN);
- answer->setHWAddr(question->getHtype(), question->getHlen(), mac);
+ answer->setHWAddr(question->getHWAddr());
// relay address
answer->setGiaddr(question->getGiaddr());
@@ -203,21 +321,21 @@ void Dhcpv4Srv::copyDefaultFields(const Pkt4Ptr& question, Pkt4Ptr& answer) {
answer->setRemoteAddr(question->getRemoteAddr());
}
+ // Let's copy client-id to response. See RFC6842.
+ OptionPtr client_id = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (client_id) {
+ answer->addOption(client_id);
+ }
}
void Dhcpv4Srv::appendDefaultOptions(Pkt4Ptr& msg, uint8_t msg_type) {
OptionPtr opt;
// add Message Type Option (type 53)
- std::vector<uint8_t> tmp;
- tmp.push_back(static_cast<uint8_t>(msg_type));
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
- msg->addOption(opt);
+ msg->setType(msg_type);
// DHCP Server Identifier (type 54)
- opt = OptionPtr
- (new Option4AddrLst(DHO_DHCP_SERVER_IDENTIFIER, IOAddress(HARDCODED_SERVER_ID)));
- msg->addOption(opt);
+ msg->addOption(getServerID());
// more options will be added here later
}
@@ -237,25 +355,109 @@ void Dhcpv4Srv::appendRequestedOptions(Pkt4Ptr& msg) {
msg->addOption(opt);
}
-void Dhcpv4Srv::tryAssignLease(Pkt4Ptr& msg) {
- OptionPtr opt;
+void Dhcpv4Srv::assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer) {
+
+ // We need to select a subnet the client is connected in.
+ Subnet4Ptr subnet = selectSubnet(question);
+ if (!subnet) {
+ // This particular client is out of luck today. We do not have
+ // information about the subnet he is connected to. This likely means
+ // misconfiguration of the server (or some relays). We will continue to
+ // process this message, but our response will be almost useless: no
+ // addresses or prefixes, no subnet specific configuration etc. The only
+ // thing this client can get is some global information (like DNS
+ // servers).
+
+ // perhaps this should be logged on some higher level? This is most likely
+ // configuration bug.
+ LOG_ERROR(dhcp4_logger, DHCP4_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(serverReceivedPacketName(question->getType()));
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ return;
+ }
- // TODO: Implement actual lease assignment here
- msg->setYiaddr(IOAddress(HARDCODED_LEASE));
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL_DATA, DHCP4_SUBNET_SELECTED)
+ .arg(subnet->toText());
- // IP Address Lease time (type 51)
- opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
- opt->setUint32(HARDCODED_LEASE_TIME);
- msg->addOption(opt);
- // TODO: create Option_IntArray that holds list of integers, similar to Option4_AddrLst
+ // Get client-id option
+ ClientIdPtr client_id;
+ OptionPtr opt = question->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+ // client-id is not mandatory in DHCPv4
+
+ IOAddress hint = question->getYiaddr();
+
+ HWAddrPtr hwaddr = question->getHWAddr();
+
+ // "Fake" allocation is processing of DISCOVER message. We pretend to do an
+ // allocation, but we do not put the lease in the database. That is ok,
+ // because we do not guarantee that the user will get that exact lease. If
+ // the user selects this server to do actual allocation (i.e. sends REQUEST)
+ // it should include this hint. That will help us during the actual lease
+ // allocation.
+ bool fake_allocation = (question->getType() == DHCPDISCOVER);
+
+ // Use allocation engine to pick a lease for this client. Allocation engine
+ // will try to honour the hint, but it is just a hint - some other address
+ // may be used instead. If fake_allocation is set to false, the lease will
+ // be inserted into the LeaseMgr as well.
+ Lease4Ptr lease = alloc_engine_->allocateAddress4(subnet, client_id, hwaddr,
+ hint, fake_allocation);
+
+ if (lease) {
+ // We have a lease! Let's set it in the packet and send it back to
+ // the client.
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT:DHCP4_LEASE_ALLOC)
+ .arg(lease->addr_.toText())
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)");
+
+ answer->setYiaddr(lease->addr_);
+
+ // IP Address Lease time (type 51)
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_LEASE_TIME));
+ opt->setUint32(lease->valid_lft_);
+ answer->addOption(opt);
+
+ // @todo: include real router information here
+ // Router (type 3)
+ opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
+ answer->addOption(opt);
+
+ // Subnet mask (type 1)
+ answer->addOption(getNetmaskOption(subnet));
+
+ // @todo: send renew timer option (T1, option 58)
+ // @todo: send rebind timer option (T2, option 59)
- // Subnet mask (type 1)
- opt = OptionPtr(new Option4AddrLst(DHO_SUBNET_MASK, IOAddress(HARDCODED_NETMASK)));
- msg->addOption(opt);
+ } else {
+ // Allocation engine did not allocate a lease. The engine logged
+ // cause of that failure. The only thing left is to insert
+ // status code to pass the sad news to the client.
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, fake_allocation?
+ DHCP4_LEASE_ADVERT_FAIL:DHCP4_LEASE_ALLOC_FAIL)
+ .arg(client_id?client_id->toText():"(no client-id)")
+ .arg(hwaddr?hwaddr->toText():"(no hwaddr info)")
+ .arg(hint.toText());
+
+ answer->setType(DHCPNAK);
+ answer->setYiaddr(IOAddress("0.0.0.0"));
+ }
+}
- // Router (type 3)
- opt = OptionPtr(new Option4AddrLst(DHO_ROUTERS, IOAddress(HARDCODED_GATEWAY)));
- msg->addOption(opt);
+OptionPtr Dhcpv4Srv::getNetmaskOption(const Subnet4Ptr& subnet) {
+ uint32_t netmask = getNetmask4(subnet->get().second);
+
+ OptionPtr opt(new OptionInt<uint32_t>(Option::V4,
+ DHO_SUBNET_MASK, netmask));
+
+ return (opt);
}
Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
@@ -266,7 +468,7 @@ Pkt4Ptr Dhcpv4Srv::processDiscover(Pkt4Ptr& discover) {
appendDefaultOptions(offer, DHCPOFFER);
appendRequestedOptions(offer);
- tryAssignLease(offer);
+ assignLease(discover, offer);
return (offer);
}
@@ -279,13 +481,77 @@ Pkt4Ptr Dhcpv4Srv::processRequest(Pkt4Ptr& request) {
appendDefaultOptions(ack, DHCPACK);
appendRequestedOptions(ack);
- tryAssignLease(ack);
+ assignLease(request, ack);
return (ack);
}
void Dhcpv4Srv::processRelease(Pkt4Ptr& release) {
- /// TODO: Implement this.
+
+ // Try to find client-id
+ ClientIdPtr client_id;
+ OptionPtr opt = release->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ if (opt) {
+ client_id = ClientIdPtr(new ClientId(opt->getData()));
+ }
+
+ try {
+ // Do we have a lease for that particular address?
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(release->getYiaddr());
+
+ if (!lease) {
+ // No such lease - bogus release
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_NO_LEASE)
+ .arg(release->getYiaddr().toText())
+ .arg(release->getHWAddr()->toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)");
+ return;
+ }
+
+ // Does the hardware address match? We don't want one client releasing
+ // second client's leases.
+ if (lease->hwaddr_ != release->getHWAddr()->hwaddr_) {
+ // @todo: Print hwaddr from lease as part of ticket #2589
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_HWADDR)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ return;
+ }
+
+ // Does the lease have client-id info? If it has, then check it with what
+ // the client sent us.
+ if (lease->client_id_ && client_id && *lease->client_id_ != *client_id) {
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE_FAIL_WRONG_CLIENT_ID)
+ .arg(release->getYiaddr().toText())
+ .arg(client_id->toText())
+ .arg(lease->client_id_->toText());
+ return;
+ }
+
+ // Ok, hw and client-id match - let's release the lease.
+ if (LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+
+ // Release successful - we're done here
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_DETAIL, DHCP4_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ } else {
+
+ // Release failed -
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(client_id ? client_id->toText() : "(no client-id)")
+ .arg(release->getHWAddr()->toText());
+ }
+ } catch (const isc::Exception& ex) {
+ // Rethrow the exception with a bit more data.
+ LOG_ERROR(dhcp4_logger, DHCP4_RELEASE_EXCEPTION)
+ .arg(ex.what())
+ .arg(release->getYiaddr());
+ }
+
}
void Dhcpv4Srv::processDecline(Pkt4Ptr& decline) {
@@ -327,3 +593,42 @@ Dhcpv4Srv::serverReceivedPacketName(uint8_t type) {
}
return (UNKNOWN);
}
+
+Subnet4Ptr Dhcpv4Srv::selectSubnet(const Pkt4Ptr& question) {
+
+ // Is this relayed message?
+ IOAddress relay = question->getGiaddr();
+ if (relay.toText() == "0.0.0.0") {
+
+ // Yes: Use relay address to select subnet
+ return (CfgMgr::instance().getSubnet4(relay));
+ } else {
+
+ // No: Use client's address to select subnet
+ return (CfgMgr::instance().getSubnet4(question->getRemoteAddr()));
+ }
+}
+
+void Dhcpv4Srv::sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid) {
+ OptionPtr server_id = pkt->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ switch (serverid) {
+ case FORBIDDEN:
+ if (server_id) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
+ << "received in " << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case MANDATORY:
+ if (!server_id) {
+ isc_throw(RFCViolation, "Server-id option was expected, but not "
+ " received in message "
+ << serverReceivedPacketName(pkt->getType()));
+ }
+ break;
+
+ case OPTIONAL:
+ // do nothing here
+ ;
+ }
+}
diff --git a/src/bin/dhcp4/dhcp4_srv.h b/src/bin/dhcp4/dhcp4_srv.h
index db4b4bc..8d26e05 100644
--- a/src/bin/dhcp4/dhcp4_srv.h
+++ b/src/bin/dhcp4/dhcp4_srv.h
@@ -18,14 +18,25 @@
#include <dhcp/dhcp4.h>
#include <dhcp/pkt4.h>
#include <dhcp/option.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/alloc_engine.h>
#include <boost/noncopyable.hpp>
#include <iostream>
namespace isc {
-
namespace dhcp {
+
+/// @brief file name of a server-id file
+///
+/// Server must store its server identifier in persistent storage that must not
+/// change between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// regular IPv4 address, e.g. 192.0.2.1. Server will create it during
+/// first run and then use it afterwards.
+static const char* SERVER_ID_FILE = "b10-dhcp4-serverid";
+
/// @brief DHCPv4 server service.
///
/// This singleton class represents DHCPv4 server. It contains all
@@ -44,6 +55,14 @@ namespace dhcp {
class Dhcpv4Srv : public boost::noncopyable {
public:
+
+ /// @brief defines if certain option may, must or must not appear
+ typedef enum {
+ FORBIDDEN,
+ MANDATORY,
+ OPTIONAL
+ } RequirementLevel;
+
/// @brief Default constructor.
///
/// Instantiates necessary services, required to run DHCPv4 server.
@@ -54,7 +73,10 @@ class Dhcpv4Srv : public boost::noncopyable {
/// for testing purposes.
///
/// @param port specifies port number to listen on
- Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT);
+ /// @param dbconfig Lease manager configuration string. The default
+ /// of the "memfile" manager is used for testing.
+ Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv4 service shutdown.
~Dhcpv4Srv();
@@ -82,6 +104,8 @@ class Dhcpv4Srv : public boost::noncopyable {
/// As the operation of the method does not depend on any server state, it
/// is declared static.
///
+ /// @todo: This should be named static Pkt4::getName()
+ ///
/// @param type DHCPv4 packet type
///
/// @return Pointer to "const" string containing the packet name.
@@ -90,6 +114,17 @@ class Dhcpv4Srv : public boost::noncopyable {
static const char* serverReceivedPacketName(uint8_t type);
protected:
+
+ /// @brief verifies if specified packet meets RFC requirements
+ ///
+ /// Checks if mandatory option is really there, that forbidden option
+ /// is not there, and that client-id or server-id appears only once.
+ ///
+ /// @param pkt packet to be checked
+ /// @param serverid expectation regarding server-id option
+ /// @throw RFCViolation if any issues are detected
+ void sanityCheck(const Pkt4Ptr& pkt, RequirementLevel serverid);
+
/// @brief Processes incoming DISCOVER and returns response.
///
/// Processes received DISCOVER message and verifies that its sender
@@ -156,11 +191,19 @@ protected:
/// client and assigning it. Options corresponding to the lease
/// are added to specific message.
///
- /// Note: Lease manager is not implemented yet, so this method
- /// used fixed, hardcoded lease.
+ /// @param question DISCOVER or REQUEST message from client
+ /// @param answer OFFER or ACK/NAK message (lease options will be added here)
+ void assignLease(const Pkt4Ptr& question, Pkt4Ptr& answer);
+
+ /// @brief Attempts to renew received addresses
+ ///
+ /// Attempts to renew existing lease. This typically includes finding a lease that
+ /// corresponds to the received address. If no such lease is found, a status code
+ /// response is generated.
///
- /// @param msg OFFER or ACK message (lease options will be added here)
- void tryAssignLease(Pkt4Ptr& msg);
+ /// @param renew client's message asking for renew
+ /// @param reply server's response (ACK or NAK)
+ void renewLease(const Pkt4Ptr& renew, Pkt4Ptr& reply);
/// @brief Appends default options to a message
///
@@ -182,7 +225,38 @@ protected:
///
/// @throws isc::Unexpected Failed to obtain server identifier (i.e. no
// previously stored configuration and no network interfaces available)
- void setServerID();
+ void generateServerID();
+
+ /// @brief attempts to load server-id from a file
+ ///
+ /// Tries to load duid from a text file. If the load is successful,
+ /// it creates server-id option and stores it in serverid_ (to be used
+ /// later by getServerID()).
+ ///
+ /// @param file_name name of the server-id file to load
+ /// @return true if load was successful, false otherwise
+ bool loadServerID(const std::string& file_name);
+
+ /// @brief attempts to write server-id to a file
+ /// Tries to write server-id content (stored in serverid_) to a text file.
+ ///
+ /// @param file_name name of the server-id file to write
+ /// @return true if write was successful, false otherwise
+ bool writeServerID(const std::string& file_name);
+
+ /// @brief converts server-id to text
+ /// Converts content of server-id option to a text representation, e.g.
+ /// "192.0.2.1"
+ ///
+ /// @param opt option that contains server-id
+ /// @return string representation
+ static std::string srvidToString(const OptionPtr& opt);
+
+ /// @brief Selects a subnet for a given client's packet.
+ ///
+ /// @param question client's message
+ /// @return selected subnet (or NULL if no suitable subnet was found)
+ isc::dhcp::Subnet4Ptr selectSubnet(const Pkt4Ptr& question);
/// server DUID (to be sent in server-identifier option)
OptionPtr serverid_;
@@ -190,6 +264,21 @@ protected:
/// indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
volatile bool shutdown_;
+
+ private:
+
+ /// @brief Constructs netmask option based on subnet4
+ /// @param subnet subnet for which the netmask will be calculated
+ ///
+ /// @return Option that contains netmask information
+ static OptionPtr getNetmaskOption(const Subnet4Ptr& subnet);
+
+ /// @brief Allocation Engine.
+ /// Pointer to the allocation engine that we are currently using
+ /// It must be a pointer, because we will support changing engines
+ /// during normal operation (e.g. to use different allocators)
+ boost::shared_ptr<AllocEngine> alloc_engine_;
+
};
}; // namespace isc::dhcp
diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc
index 3dd75d7..ba14edf 100644
--- a/src/bin/dhcp4/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp4/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,9 +17,10 @@
#include <arpa/inet.h>
#include <gtest/gtest.h>
+#include <config/ccsession.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/config_parser.h>
-#include <config/ccsession.h>
+#include <dhcp/option4_addrlst.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <boost/foreach.hpp>
@@ -35,12 +36,6 @@ 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 {
@@ -55,7 +50,9 @@ public:
// 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);
+ const std::map<std::string, uint32_t>& uint32_defaults = getUint32Defaults();
+ std::map<std::string, uint32_t>::const_iterator it =
+ uint32_defaults.find(name);
if (it == uint32_defaults.end()) {
ADD_FAILURE() << "Expected uint32 with name " << name
<< " not found";
@@ -81,7 +78,8 @@ public:
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data",
+ /// "csv-format" and "space".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -94,16 +92,34 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
+ params["space"] = "dhcp4";
params["code"] = "56";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "dhcp-message";
+ params["space"] = param_value;
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
params["code"] = "56";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "dhcp-message";
+ params["space"] = "dhcp4";
+ params["code"] = "56";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
@@ -136,10 +152,14 @@ public:
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
stream << "\"code\": " << param.second << "";
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -226,6 +246,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet4\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -393,9 +414,9 @@ TEST_F(Dhcp4ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
- checkResult(status, 2);
+ checkResult(status, 1);
}
// Goal of this test is to verify if pools can be defined
@@ -428,6 +449,363 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv4 address can be created.
+TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp4ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp4ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp4ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp4ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp4 option
+/// space) and that it is allowed to define option in the dhcp4
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 109 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp4 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 109,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp4", 109);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(109, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp4' option space groups
+ // standard options and the code 100 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp4\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -437,14 +815,18 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
@@ -461,11 +843,11 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -490,6 +872,74 @@ TEST_F(Dhcp4ParserTest, optionDataDefaults) {
testOption(*range.first, 23, foo2_expected, sizeof(foo2_expected));
}
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp4' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
+ " \"code\": 56,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 56,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 56,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet4\": [ { "
+ " \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
+ " \"subnet\": \"192.0.2.0/24\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.200"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp4.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp4", 56);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 56);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(56, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 56);
+ ASSERT_FALSE(desc3.option);
+}
+
// Goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
@@ -500,22 +950,28 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB\""
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet4\": [ { "
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"AB CDEF0105\""
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
- " \"data\": \"01\""
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -529,11 +985,11 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.24"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -569,18 +1025,22 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"192.0.2.1 - 192.0.2.100\" ],"
" \"subnet\": \"192.0.2.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
+ " \"name\": \"dhcp-message\","
+ " \"space\": \"dhcp4\","
" \"code\": 56,"
- " \"data\": \"0102030405060708090A\""
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
" \"pool\": [ \"192.0.3.101 - 192.0.3.150\" ],"
" \"subnet\": \"192.0.3.0/24\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
+ " \"name\": \"default-ip-ttl\","
+ " \"space\": \"dhcp4\","
" \"code\": 23,"
- " \"data\": \"FF\""
+ " \"data\": \"FF\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -594,11 +1054,11 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
Subnet4Ptr subnet1 = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.100"));
ASSERT_TRUE(subnet1);
- const Subnet::OptionContainer& options1 = subnet1->getOptions();
- ASSERT_EQ(1, options1.size());
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -618,10 +1078,10 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
// Test another subnet in the same way.
Subnet4Ptr subnet2 = CfgMgr::instance().getSubnet4(IOAddress("192.0.3.102"));
ASSERT_TRUE(subnet2);
- const Subnet::OptionContainer& options2 = subnet2->getOptions();
- ASSERT_EQ(1, options2.size());
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options2->size());
- const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
idx2.equal_range(23);
@@ -703,11 +1163,11 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp4");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -724,10 +1184,73 @@ TEST_F(Dhcp4ParserTest, optionDataLowerCase) {
testOption(*range.first, 56, foo_expected, sizeof(foo_expected));
}
+// Verify that specific option object is returned for standard
+// option which has dedicated option class derived from Option.
+TEST_F(Dhcp4ParserTest, stdOptionData) {
+ ConstElementPtr x;
+ std::map<std::string, std::string> params;
+ params["name"] = "nis-servers";
+ params["space"] = "dhcp4";
+ // Option code 41 means nis-servers.
+ params["code"] = "41";
+ // Specify option values in a CSV (user friendly) format.
+ params["data"] = "192.0.2.10, 192.0.2.1, 192.0.2.3";
+ params["csv-format"] = "True";
+
+ std::string config = createConfigWithOption(params);
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+ ASSERT_TRUE(x);
+ comment_ = parseAnswer(rcode_, x);
+ ASSERT_EQ(0, rcode_);
+
+ Subnet4Ptr subnet = CfgMgr::instance().getSubnet4(IOAddress("192.0.2.5"));
+ ASSERT_TRUE(subnet);
+ Subnet::OptionContainerPtr options =
+ subnet->getOptionDescriptors("dhcp4");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(1, options->size());
+
+ // Get the search index. Index #1 is to search using option code.
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
+
+ // Get the options for specified index. Expecting one option to be
+ // returned but in theory we may have multiple options with the same
+ // code so we get the range.
+ std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
+ Subnet::OptionContainerTypeIndex::const_iterator> range =
+ idx.equal_range(DHO_NIS_SERVERS);
+ // Expect single option with the code equal to NIS_SERVERS option code.
+ ASSERT_EQ(1, std::distance(range.first, range.second));
+ // The actual pointer to the option is held in the option field
+ // in the structure returned.
+ OptionPtr option = range.first->option;
+ ASSERT_TRUE(option);
+ // Option object returned for here is expected to be Option6IA
+ // which is derived from Option. This class is dedicated to
+ // represent standard option IA_NA.
+ boost::shared_ptr<Option4AddrLst> option_addrs =
+ boost::dynamic_pointer_cast<Option4AddrLst>(option);
+ // If cast is unsuccessful than option returned was of a
+ // differnt type than Option6IA. This is wrong.
+ ASSERT_TRUE(option_addrs);
+
+ // Get addresses from the option.
+ Option4AddrLst::AddressContainer addrs = option_addrs->getAddresses();
+ // Verify that the addresses have been configured correctly.
+ ASSERT_EQ(3, addrs.size());
+ EXPECT_EQ("192.0.2.10", addrs[0].toText());
+ EXPECT_EQ("192.0.2.1", addrs[1].toText());
+ EXPECT_EQ("192.0.2.3", addrs[2].toText());
+}
+
/// This test checks if Uint32Parser can really parse the whole range
/// and properly err of out of range values. As we can't call Uint32Parser
/// directly, we are exploiting the fact that it is used to parse global
/// parameter renew-timer and the results are stored in uint32_defaults.
+/// We get the uint32_defaults using a getUint32Defaults functions which
+/// is defined only to access the values from this test.
TEST_F(Dhcp4ParserTest, DISABLED_Uint32Parser) {
ConstElementPtr status;
diff --git a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
index ac6f0bb..a2331ff 100644
--- a/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -19,7 +19,11 @@
#include <dhcp/dhcp4.h>
#include <dhcp/option.h>
#include <dhcp4/dhcp4_srv.h>
-
+#include <dhcp4/dhcp4_log.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <gtest/gtest.h>
#include <fstream>
@@ -33,43 +37,50 @@ using namespace isc::dhcp;
using namespace isc::asiolink;
namespace {
-const char* const INTERFACE_FILE = "interfaces.txt";
class NakedDhcpv4Srv: public Dhcpv4Srv {
- // "naked" DHCPv4 server, exposes internal fields
+ // "Naked" DHCPv4 server, exposes internal fields
public:
- NakedDhcpv4Srv():Dhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
-
- boost::shared_ptr<Pkt4> processDiscover(boost::shared_ptr<Pkt4>& discover) {
- return Dhcpv4Srv::processDiscover(discover);
- }
- boost::shared_ptr<Pkt4> processRequest(boost::shared_ptr<Pkt4>& request) {
- return Dhcpv4Srv::processRequest(request);
- }
- void processRelease(boost::shared_ptr<Pkt4>& release) {
- return Dhcpv4Srv::processRelease(release);
- }
- void processDecline(boost::shared_ptr<Pkt4>& decline) {
- Dhcpv4Srv::processDecline(decline);
- }
- boost::shared_ptr<Pkt4> processInform(boost::shared_ptr<Pkt4>& inform) {
- return Dhcpv4Srv::processInform(inform);
- }
+ NakedDhcpv4Srv(uint16_t port = 0):Dhcpv4Srv(port) { }
+
+ using Dhcpv4Srv::processDiscover;
+ using Dhcpv4Srv::processRequest;
+ using Dhcpv4Srv::processRelease;
+ using Dhcpv4Srv::processDecline;
+ using Dhcpv4Srv::processInform;
+ using Dhcpv4Srv::getServerID;
+ using Dhcpv4Srv::loadServerID;
+ using Dhcpv4Srv::generateServerID;
+ using Dhcpv4Srv::writeServerID;
+ using Dhcpv4Srv::sanityCheck;
+ using Dhcpv4Srv::srvidToString;
};
+static const char* SRVID_FILE = "server-id-test.txt";
+
class Dhcpv4SrvTest : public ::testing::Test {
public:
+
+ /// @brief Constructor
+ ///
+ /// Initializes common objects used in many tests.
+ /// Also sets up initial configuration in CfgMgr.
Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
- fstream fakeifaces(INTERFACE_FILE, ios::out | ios::trunc);
- if (if_nametoindex("lo") > 0) {
- fakeifaces << "lo 127.0.0.1";
- } else if (if_nametoindex("lo0") > 0) {
- fakeifaces << "lo0 127.0.0.1";
- }
- fakeifaces.close();
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1000,
+ 2000, 3000));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), IOAddress("192.0.2.110")));
+ subnet_->addPool(pool_);
+
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().addSubnet4(subnet_);
+
+ // it's ok if that fails. There should not be such a file anyway
+ unlink(SRVID_FILE);
}
+ /// @brief checks that the response matches request
+ /// @param q query (client's message)
+ /// @param a answer (server's message)
void MessageCheck(const boost::shared_ptr<Pkt4>& q,
const boost::shared_ptr<Pkt4>& a) {
ASSERT_TRUE(q);
@@ -80,7 +91,7 @@ public:
EXPECT_EQ(q->getIndex(), a->getIndex());
EXPECT_EQ(q->getGiaddr(), a->getGiaddr());
- // check that bare minimum of required options are there
+ // Check that bare minimum of required options are there
EXPECT_TRUE(a->getOption(DHO_SUBNET_MASK));
EXPECT_TRUE(a->getOption(DHO_ROUTERS));
EXPECT_TRUE(a->getOption(DHO_DHCP_SERVER_IDENTIFIER));
@@ -90,29 +101,211 @@ public:
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME));
EXPECT_TRUE(a->getOption(DHO_DOMAIN_NAME_SERVERS));
- // check that something is offered
+ // Check that something is offered
EXPECT_TRUE(a->getYiaddr().toText() != "0.0.0.0");
}
+ /// @brief generates client-id option
+ ///
+ /// Generate client-id option of specified length
+ /// Ids with different lengths are sufficent to generate
+ /// unique ids. If more fine grained control is required,
+ /// tests generate client-ids on their own.
+ /// Sets client_id_ field.
+ /// @param size size of the client-id to be generated
+ OptionPtr generateClientId(size_t size = 4) {
+
+ OptionBuffer clnt_id(size);
+ for (int i = 0; i < size; i++) {
+ clnt_id[i] = 100 + i;
+ }
+
+ client_id_ = ClientIdPtr(new ClientId(clnt_id));
+
+ return (OptionPtr(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
+ clnt_id.begin(),
+ clnt_id.begin() + size)));
+ }
+
+ /// @brief generate hardware address
+ ///
+ /// @param size size of the generated MAC address
+ /// @param pointer to Hardware Address object
+ HWAddrPtr generateHWAddr(size_t size = 6) {
+ const uint8_t hw_type = 123; // Just a fake number (typically 6=HTYPE_ETHER, see dhcp4.h)
+ OptionBuffer mac(size);
+ for (int i = 0; i < size; ++i) {
+ mac[i] = 50 + i;
+ }
+ return (HWAddrPtr(new HWAddr(mac, hw_type)));
+ }
+
+ /// Check that address was returned from proper range, that its lease
+ /// lifetime is correct, that T1 and T2 are returned properly
+ /// @param rsp response to be checked
+ /// @param subnet subnet that should be used to verify assigned address and options
+ /// @param t1_mandatory is T1 mandatory?
+ /// @param t2_mandatory is T2 mandatory?
+ void checkAddressParams(const Pkt4Ptr& rsp, const SubnetPtr subnet,
+ bool t1_mandatory = false, bool t2_mandatory = false) {
+
+ // Technically inPool implies inRange, but let's be on the safe
+ // side and check both.
+ EXPECT_TRUE(subnet->inRange(rsp->getYiaddr()));
+ EXPECT_TRUE(subnet->inPool(rsp->getYiaddr()));
+
+ // Check lease time
+ OptionPtr opt = rsp->getOption(DHO_DHCP_LEASE_TIME);
+ if (!opt) {
+ ADD_FAILURE() << "Lease time option missing in response";
+ } else {
+ EXPECT_EQ(opt->getUint32(), subnet->getValid());
+ }
+
+ // Check T1 timer
+ opt = rsp->getOption(DHO_DHCP_RENEWAL_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT1());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T1 option missing";
+ }
+ }
+
+ // Check T2 timer
+ opt = rsp->getOption(DHO_DHCP_REBINDING_TIME);
+ if (opt) {
+ EXPECT_EQ(opt->getUint32(), subnet->getT2());
+ } else {
+ if (t1_mandatory) {
+ ADD_FAILURE() << "Required T2 option missing";
+ }
+ }
+ }
+
+ /// @brief Basic checks for generated response (message type and trans-id).
+ ///
+ /// @param rsp response packet to be validated
+ /// @param expected_message_type expected message type
+ /// @param expected_transid expected transaction-id
+ void checkResponse(const Pkt4Ptr& rsp, uint8_t expected_message_type,
+ uint32_t expected_transid) {
+ ASSERT_TRUE(rsp);
+ EXPECT_EQ(expected_message_type, rsp->getType());
+ EXPECT_EQ(expected_transid, rsp->getTransid());
+ }
+
+ /// @brief Checks if the lease sent to client is present in the database
+ ///
+ /// @param rsp response packet to be validated
+ /// @param client_id expected client-identifier (or NULL)
+ /// @param HWAddr expected hardware address (not used now)
+ /// @param expected_addr expected address
+ Lease4Ptr checkLease(const Pkt4Ptr& rsp, const OptionPtr& client_id,
+ const HWAddrPtr&, const IOAddress& expected_addr) {
+
+ ClientIdPtr id;
+ if (client_id) {
+ OptionBuffer data = client_id->getData();
+ id.reset(new ClientId(data));
+ }
+
+ Lease4Ptr lease = LeaseMgrFactory::instance().getLease4(expected_addr);
+ if (!lease) {
+ cout << "Lease for " << expected_addr.toText()
+ << " not found in the database backend.";
+ return (Lease4Ptr());
+ }
+
+ EXPECT_EQ(rsp->getYiaddr().toText(), expected_addr.toText());
+
+ EXPECT_EQ(expected_addr.toText(), lease->addr_.toText());
+ if (client_id) {
+ EXPECT_TRUE(*lease->client_id_ == *id);
+ }
+ EXPECT_EQ(subnet_->getID(), lease->subnet_id_);
+
+ return (lease);
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper server-id
+ /// @param rsp response packet to be validated
+ /// @param expected_srvid expected value of server-id
+ void checkServerId(const Pkt4Ptr& rsp, const OptionPtr& expected_srvid) {
+ // Check that server included its server-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_SERVER_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt->getType(), expected_srvid->getType() );
+ EXPECT_EQ(opt->len(), expected_srvid->len() );
+ EXPECT_TRUE(opt->getData() == expected_srvid->getData());
+ }
+
+ /// @brief Checks if server response (OFFER, ACK, NAK) includes proper client-id
+ /// @param rsp response packet to be validated
+ /// @param expected_clientid expected value of client-id
+ void checkClientId(const Pkt4Ptr& rsp, const OptionPtr& expected_clientid) {
+ // check that server included our own client-id
+ OptionPtr opt = rsp->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(expected_clientid->getType(), opt->getType());
+ EXPECT_EQ(expected_clientid->len(), opt->len());
+ EXPECT_TRUE(expected_clientid->getData() == opt->getData());
+ }
+
~Dhcpv4SrvTest() {
- unlink(INTERFACE_FILE);
+ CfgMgr::instance().deleteSubnets4();
+
+ // Let's clean up if there is such a file.
+ unlink(SRVID_FILE);
};
+
+ /// @brief A subnet used in most tests
+ Subnet4Ptr subnet_;
+
+ /// @brief A pool used in most tests
+ Pool4Ptr pool_;
+
+ /// @brief A client-id used in most tests
+ ClientIdPtr client_id_;
};
+// Sanity check. Verifies that both Dhcpv4Srv and its derived
+// class NakedDhcpv4Srv can be instantiated and destroyed.
TEST_F(Dhcpv4SrvTest, basic) {
- // nothing to test. DHCPv4_srv instance is created
- // in test fixture. It is destroyed in destructor
+ // Check that the base class can be instantiated
Dhcpv4Srv* srv = NULL;
ASSERT_NO_THROW({
srv = new Dhcpv4Srv(DHCP4_SERVER_PORT + 10000);
});
-
delete srv;
+
+ // Check that the derived class can be instantiated
+ NakedDhcpv4Srv* naked_srv = NULL;
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(DHCP4_SERVER_PORT + 10000);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+ delete naked_srv;
+
+ ASSERT_NO_THROW({
+ naked_srv = new NakedDhcpv4Srv(0);
+ });
+ EXPECT_TRUE(naked_srv->getServerID());
+
+ delete naked_srv;
}
+// Verifies that received DISCOVER can be processed correctly,
+// that the OFFER message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See DiscoverBasic, DiscoverHint, DiscoverNoClientId
+// and DiscoverInvalidHint.
TEST_F(Dhcpv4SrvTest, processDiscover) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = 255 - i;
@@ -127,26 +320,26 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
pkt->setRemoteAddr(IOAddress("192.0.2.56"));
pkt->setGiaddr(IOAddress("192.0.2.67"));
- // let's make it a relayed message
+ // Let's make it a relayed message
pkt->setHops(3);
pkt->setRemotePort(DHCP4_SERVER_PORT);
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(pkt->getGiaddr(), offer->getRemoteAddr());
MessageCheck(pkt, offer);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
pkt->setHops(0);
pkt->setGiaddr(IOAddress("0.0.0.0"));
pkt->setRemotePort(DHCP4_CLIENT_PORT);
@@ -155,12 +348,12 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
offer = srv->processDiscover(pkt);
);
- // should return something
+ // Should return something
ASSERT_TRUE(offer);
EXPECT_EQ(DHCPOFFER, offer->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(pkt->getRemoteAddr(), offer->getRemoteAddr());
@@ -169,8 +362,15 @@ TEST_F(Dhcpv4SrvTest, processDiscover) {
delete srv;
}
+// Verifies that received REQUEST can be processed correctly,
+// that the ACK message generated in response is valid and
+// contains necessary options.
+//
+// Note: this test focuses on the packet correctness. There
+// are other tests that verify correctness of the allocation
+// engine. See RequestBasic.
TEST_F(Dhcpv4SrvTest, processRequest) {
- NakedDhcpv4Srv* srv = new NakedDhcpv4Srv();
+ NakedDhcpv4Srv* srv = new NakedDhcpv4Srv(0);
vector<uint8_t> mac(6);
for (int i = 0; i < 6; i++) {
mac[i] = i*10;
@@ -185,22 +385,22 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
req->setRemoteAddr(IOAddress("192.0.2.56"));
req->setGiaddr(IOAddress("192.0.2.67"));
- // should not throw
+ // Should not throw
ASSERT_NO_THROW(
ack = srv->processRequest(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPACK, ack->getType());
- // this is relayed message. It should be sent back to relay address.
+ // This is relayed message. It should be sent back to relay address.
EXPECT_EQ(req->getGiaddr(), ack->getRemoteAddr());
MessageCheck(req, ack);
- // now repeat the test for directly sent message
+ // Now repeat the test for directly sent message
req->setHops(0);
req->setGiaddr(IOAddress("0.0.0.0"));
req->setRemotePort(DHCP4_CLIENT_PORT);
@@ -209,12 +409,12 @@ TEST_F(Dhcpv4SrvTest, processRequest) {
ack = srv->processDiscover(req);
);
- // should return something
+ // Should return something
ASSERT_TRUE(ack);
EXPECT_EQ(DHCPOFFER, ack->getType());
- // this is direct message. It should be sent back to origin, not
+ // This is direct message. It should be sent back to origin, not
// to relay.
EXPECT_EQ(ack->getRemoteAddr(), req->getRemoteAddr());
@@ -228,14 +428,11 @@ TEST_F(Dhcpv4SrvTest, processRelease) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPRELEASE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processRelease(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
-
delete srv;
}
@@ -244,13 +441,11 @@ TEST_F(Dhcpv4SrvTest, processDecline) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPDECLINE, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processDecline(pkt);
);
- // TODO: Implement more reasonable tests before starting
- // work on processSomething() method.
delete srv;
}
@@ -259,15 +454,15 @@ TEST_F(Dhcpv4SrvTest, processInform) {
boost::shared_ptr<Pkt4> pkt(new Pkt4(DHCPINFORM, 1234));
- // should not throw
+ // Should not throw
EXPECT_NO_THROW(
srv->processInform(pkt);
);
- // should return something
+ // Should return something
EXPECT_TRUE(srv->processInform(pkt));
- // TODO: Implement more reasonable tests before starting
+ // @todo Implement more reasonable tests before starting
// work on processSomething() method.
delete srv;
@@ -305,4 +500,672 @@ TEST_F(Dhcpv4SrvTest, serverReceivedPacketName) {
}
}
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - address set to specific value as hint
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address
+TEST_F(Dhcpv4SrvTest, DiscoverNoClientId) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("192.0.2.107");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_EQ(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+}
+
+// This test verifies that incoming DISCOVER can be handled properly, that an
+// OFFER is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed very simple DISCOVER message with:
+// - client-id option
+// - address set to specific value as hint, but that hint is invalid
+//
+// expected returned OFFER message:
+// - copy of client-id
+// - server-id
+// - offered address (!= hint)
+TEST_F(Dhcpv4SrvTest, DiscoverInvalidHint) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+ IOAddress hint("10.1.2.3");
+
+ Pkt4Ptr dis = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ dis->setRemoteAddr(IOAddress("192.0.2.107"));
+ OptionPtr clientid = generateClientId();
+ dis->addOption(clientid);
+ dis->setYiaddr(hint);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer = srv->processDiscover(dis);
+
+ // Check if we get response at all
+ checkResponse(offer, DHCPOFFER, 1234);
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(offer, subnet_);
+
+ EXPECT_NE(offer->getYiaddr().toText(), hint.toText());
+
+ // Check identifiers
+ checkServerId(offer, srv->getServerID());
+ checkClientId(offer, clientid);
+}
+
+/// @todo: Add a test that client sends hint that is in pool, but currently
+/// being used by a different client.
+
+// This test checks that the server is offering different addresses to different
+// clients in OFFERs. Please note that OFFER is not a guarantee that such
+// an address will be assigned. Had the pool was very small and contained only
+// 2 addresses, the third client would get the same offer as the first one
+// and this is a correct behavior. It is REQUEST that will fail for the third
+// client. OFFER is basically saying "if you send me a request, you will
+// probably get an address like this" (there are no guarantees).
+TEST_F(Dhcpv4SrvTest, ManyDiscovers) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr dis1 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+ Pkt4Ptr dis2 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 2345));
+ Pkt4Ptr dis3 = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 3456));
+
+ dis1->setRemoteAddr(IOAddress("192.0.2.1"));
+ dis2->setRemoteAddr(IOAddress("192.0.2.2"));
+ dis3->setRemoteAddr(IOAddress("192.0.2.3"));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ dis1->addOption(clientid1);
+ dis2->addOption(clientid2);
+ dis3->addOption(clientid3);
+
+ // Pass it to the server and get an offer
+ Pkt4Ptr offer1 = srv->processDiscover(dis1);
+ Pkt4Ptr offer2 = srv->processDiscover(dis2);
+ Pkt4Ptr offer3 = srv->processDiscover(dis3);
+
+ // Check if we get response at all
+ checkResponse(offer1, DHCPOFFER, 1234);
+ checkResponse(offer2, DHCPOFFER, 2345);
+ checkResponse(offer3, DHCPOFFER, 3456);
+
+ IOAddress addr1 = offer1->getYiaddr();
+ IOAddress addr2 = offer2->getYiaddr();
+ IOAddress addr3 = offer3->getYiaddr();
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(offer1, subnet_);
+ checkAddressParams(offer2, subnet_);
+ checkAddressParams(offer3, subnet_);
+
+ // Check server-ids
+ checkServerId(offer1, srv->getServerID());
+ checkServerId(offer2, srv->getServerID());
+ checkServerId(offer3, srv->getServerID());
+ checkClientId(offer1, clientid1);
+ checkClientId(offer2, clientid2);
+ checkClientId(offer3, clientid3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed a single REQUEST message with:
+// - client-id option
+// - hwaddr information
+// - requested address (that the client received in DISCOVER/OFFER exchange)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address
+//
+// Test verifies that the lease is actually in the database.
+TEST_F(Dhcpv4SrvTest, RequestBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ IOAddress hint("192.0.2.107");
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress("192.0.2.1"));
+ OptionPtr clientid = generateClientId();
+ req->addOption(clientid);
+ req->setYiaddr(hint);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(hint.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = checkLease(ack, clientid, req->getHWAddr(), hint);
+
+ ASSERT_TRUE(l);
+ LeaseMgrFactory::instance().deleteLease(l->addr_);
+}
+
+// This test verifies that incoming REQUEST can be handled properly, that an
+// ACK is generated, that the response has an address and that address
+// really belongs to the configured pool.
+//
+// constructed 3 REQUEST messages with:
+// - client-id option (differs between messages)
+// - hwaddr information (differs between messages)
+//
+// expected returned ACK message:
+// - copy of client-id
+// - server-id
+// - assigned address (different for each client)
+TEST_F(Dhcpv4SrvTest, ManyRequests) {
+
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress req_addr1("192.0.2.105");
+ const IOAddress req_addr2("192.0.2.101");
+ const IOAddress req_addr3("192.0.2.109");
+ const IOAddress relay("192.0.2.1");
+
+ Pkt4Ptr req1 = Pkt4Ptr(new Pkt4(DHCPOFFER, 1234));
+ Pkt4Ptr req2 = Pkt4Ptr(new Pkt4(DHCPOFFER, 2345));
+ Pkt4Ptr req3 = Pkt4Ptr(new Pkt4(DHCPOFFER, 3456));
+
+ req1->setRemoteAddr(relay);
+ req2->setRemoteAddr(relay);
+ req3->setRemoteAddr(relay);
+
+ req1->setYiaddr(req_addr1);
+ req2->setYiaddr(req_addr2);
+ req3->setYiaddr(req_addr3);
+
+ req1->setHWAddr(generateHWAddr(6));
+ req2->setHWAddr(generateHWAddr(7));
+ req3->setHWAddr(generateHWAddr(8));
+
+ // Different client-id sizes
+ OptionPtr clientid1 = generateClientId(4); // length 4
+ OptionPtr clientid2 = generateClientId(5); // length 5
+ OptionPtr clientid3 = generateClientId(6); // length 6
+
+ req1->addOption(clientid1);
+ req2->addOption(clientid2);
+ req3->addOption(clientid3);
+
+ // Pass it to the server and get an advertise
+ Pkt4Ptr ack1 = srv->processRequest(req1);
+ Pkt4Ptr ack2 = srv->processRequest(req2);
+ Pkt4Ptr ack3 = srv->processRequest(req3);
+
+ // Check if we get response at all
+ checkResponse(ack1, DHCPACK, 1234);
+ checkResponse(ack2, DHCPACK, 2345);
+ checkResponse(ack3, DHCPACK, 3456);
+
+ IOAddress addr1 = ack1->getYiaddr();
+ IOAddress addr2 = ack2->getYiaddr();
+ IOAddress addr3 = ack3->getYiaddr();
+
+ // Check that every client received the address it requested
+ EXPECT_EQ(req_addr1.toText(), addr1.toText());
+ EXPECT_EQ(req_addr2.toText(), addr2.toText());
+ EXPECT_EQ(req_addr3.toText(), addr3.toText());
+
+ // Check that the assigned address is indeed from the configured pool
+ checkAddressParams(ack1, subnet_);
+ checkAddressParams(ack2, subnet_);
+ checkAddressParams(ack3, subnet_);
+
+ // Check DUIDs
+ checkServerId(ack1, srv->getServerID());
+ checkServerId(ack2, srv->getServerID());
+ checkServerId(ack3, srv->getServerID());
+ checkClientId(ack1, clientid1);
+ checkClientId(ack2, clientid2);
+ checkClientId(ack3, clientid3);
+
+ // Check that leases are in the database
+ Lease4Ptr l = checkLease(ack1, clientid1, req1->getHWAddr(), addr1);
+ EXPECT_TRUE(l);
+ l = checkLease(ack2, clientid2, req2->getHWAddr(), addr2);
+ l = checkLease(ack3, clientid3, req3->getHWAddr(), addr3);
+
+ // Finally check that the addresses offered are different
+ EXPECT_NE(addr1.toText(), addr2.toText());
+ EXPECT_NE(addr2.toText(), addr3.toText());
+ EXPECT_NE(addr3.toText(), addr1.toText());
+ cout << "Offered address to client1=" << addr1.toText() << endl;
+ cout << "Offered address to client2=" << addr2.toText() << endl;
+ cout << "Offered address to client3=" << addr3.toText() << endl;
+}
+
+
+// This test verifies that incoming (positive) REQUEST/Renewing can be handled properly, that a
+// REPLY is generated, that the response has an address and that address
+// really belongs to the configured pool and that lease is actually renewed.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes IAADDR
+// - lease is actually renewed in LeaseMgr
+TEST_F(Dhcpv4SrvTest, RenewBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also sets client_id_ member
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt really set.
+ // Constructed lease looks as if it was assigned 10 seconds ago
+ // EXPECT_EQ(l->t1_, temp_t1);
+ // EXPECT_EQ(l->t2_, temp_t2);
+ EXPECT_EQ(l->valid_lft_, temp_valid);
+ EXPECT_EQ(l->cltt_, temp_timestamp);
+
+ // Let's create a RENEW
+ Pkt4Ptr req = Pkt4Ptr(new Pkt4(DHCPREQUEST, 1234));
+ req->setRemoteAddr(IOAddress(addr));
+ req->setYiaddr(addr);
+ req->setCiaddr(addr); // client's address
+
+ req->addOption(clientid);
+ req->addOption(srv->getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt4Ptr ack = srv->processRequest(req);
+
+ // Check if we get response at all
+ checkResponse(ack, DHCPACK, 1234);
+ EXPECT_EQ(addr.toText(), ack->getYiaddr().toText());
+
+ // Check that address was returned from proper range, that its lease
+ // lifetime is correct, that T1 and T2 are returned properly
+ checkAddressParams(ack, subnet_);
+
+ // Check identifiers
+ checkServerId(ack, srv->getServerID());
+ checkClientId(ack, clientid);
+
+ // Check that the lease is really in the database
+ l = checkLease(ack, clientid, req->getHWAddr(), addr);
+ ASSERT_TRUE(l);
+
+ // Check that T1, T2, preferred, valid and cltt were really updated
+ EXPECT_EQ(l->t1_, subnet_->getT1());
+ EXPECT_EQ(l->t2_, subnet_->getT2());
+ EXPECT_EQ(l->valid_lft_, subnet_->getValid());
+
+ // Checking for CLTT is a bit tricky if we want to avoid off by 1 errors
+ int32_t cltt = static_cast<int32_t>(l->cltt_);
+ int32_t expected = static_cast<int32_t>(time(NULL));
+ // Equality or difference by 1 between cltt and expected is ok.
+ EXPECT_GE(1, abs(cltt - expected));
+
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
+// @todo: Implement tests for rejecting renewals
+
+// This test verifies if the sanityCheck() really checks options presence.
+TEST_F(Dhcpv4SrvTest, sanityCheck) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 1234));
+
+ // Client-id is optional for information-request, so
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::OPTIONAL));
+
+ // Empty packet, no server-id
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY), RFCViolation);
+
+ pkt->addOption(srv->getServerID());
+
+ // Server-id is mandatory and present = no exception
+ EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::MANDATORY));
+
+ // Server-id is forbidden, but present => exception
+ EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv4Srv::FORBIDDEN),
+ RFCViolation);
+}
+
+// This test verifies that incoming (positive) RELEASE can be handled properly.
+// As there is no REPLY in DHCPv4, the only thing to verify here is that
+// the lease is indeed removed from the database.
+TEST_F(Dhcpv4SrvTest, ReleaseBasic) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t temp_t1 = 50;
+ const uint32_t temp_t2 = 75;
+ const uint32_t temp_valid = 100;
+ const time_t temp_timestamp = time(NULL) - 10;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ temp_valid, temp_t1, temp_t2, temp_timestamp,
+ subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(hw);
+
+ // Pass it to the server and hope for a REPLY
+ // Note: this is no response to RELEASE in DHCPv4
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // The lease should be gone from LeaseMgr
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+
+ // Try to get the lease by hardware address
+ // @todo: Uncomment this once trac2592 is implemented
+ // Lease4Collection leases = LeaseMgrFactory::instance().getLease4(hw->hwaddr_);
+ // EXPECT_EQ(leases.size(), 0);
+
+ // Try to get it by hw/subnet_id combination
+ l = LeaseMgrFactory::instance().getLease4(hw->hwaddr_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Try by client-id
+ // @todo: Uncomment this once trac2592 is implemented
+ //Lease4Collection leases = LeaseMgrFactory::instance().getLease4(*client_id_);
+ //EXPECT_EQ(leases.size(), 0);
+
+ // Try by client-id/subnet-id
+ l = LeaseMgrFactory::instance().getLease4(*client_id_, subnet_->getID());
+ EXPECT_FALSE(l);
+
+ // Ok, the lease is *really* not there.
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+TEST_F(Dhcpv4SrvTest, ReleaseReject) {
+ boost::scoped_ptr<NakedDhcpv4Srv> srv;
+ ASSERT_NO_THROW(srv.reset(new NakedDhcpv4Srv(0)));
+
+ const IOAddress addr("192.0.2.106");
+ const uint32_t t1 = 50;
+ const uint32_t t2 = 75;
+ const uint32_t valid = 100;
+ const time_t timestamp = time(NULL) - 10;
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t bogus_mac_addr[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ HWAddrPtr bogus_hw(new HWAddr(bogus_mac_addr, sizeof(bogus_mac_addr), HTYPE_ETHER));
+ OptionPtr bogus_clientid = generateClientId(7); // different length
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Let's create a RELEASE
+ // Generate client-id also duid_
+ Pkt4Ptr rel = Pkt4Ptr(new Pkt4(DHCPRELEASE, 1234));
+ rel->setRemoteAddr(addr);
+ rel->setYiaddr(addr);
+ rel->addOption(clientid);
+ rel->addOption(srv->getServerID());
+ rel->setHWAddr(bogus_hw);
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: Lease is not known to the server");
+
+ // There is nothing to check here. The lease is not there and server does
+ // not send anything back. This case is enumerated here just for keeping
+ // parity with similar test in DHCPv6.
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // CASE 2: Lease is known and belongs to this client, but to a different hardware
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but uses different HW addr");
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t mac_addr[] = { 0, 0x1, 0x2, 0x3, 0x4, 0x5};
+ HWAddrPtr hw(new HWAddr(mac_addr, sizeof(mac_addr), HTYPE_ETHER));
+ Lease4Ptr used(new Lease4(addr, mac_addr, sizeof(mac_addr),
+ &client_id_->getDuid()[0], client_id_->getDuid().size(),
+ valid, t1, t2, timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+ // Check that the lease is really in the database
+ Lease4Ptr l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ rel->setHWAddr(bogus_hw);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease was not removed (due to hardware address mis-match)
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ rel->setHWAddr(hw); // proper HW address this time
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(bogus_clientid); // but invalid client-id
+
+ OptionPtr x = rel->getOption(DHO_DHCP_CLIENT_IDENTIFIER);
+
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(l);
+
+ // Final sanity check. Verify that with valid hw and client-id release is successful
+ rel->delOption(DHO_DHCP_CLIENT_IDENTIFIER);
+ rel->addOption(clientid);
+
+ // It should work this time
+ EXPECT_NO_THROW(srv->processRelease(rel));
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease4(addr);
+ EXPECT_FALSE(l);
+}
+
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv4SrvTest, ServerID) {
+ NakedDhcpv4Srv srv(0);
+
+ string srvid_text = "192.0.2.100";
+ IOAddress srvid(srvid_text);
+
+ fstream file1(SRVID_FILE, ios::out | ios::trunc);
+ file1 << srvid_text;
+ file1.close();
+
+ // Test reading from a file
+ EXPECT_TRUE(srv.loadServerID(SRVID_FILE));
+ ASSERT_TRUE(srv.getServerID());
+ EXPECT_EQ(srvid_text, srv.srvidToString(srv.getServerID()));
+
+ // Now test writing to a file
+ EXPECT_EQ(0, unlink(SRVID_FILE));
+ EXPECT_NO_THROW(srv.writeServerID(SRVID_FILE));
+
+ fstream file2(SRVID_FILE, ios::in);
+ ASSERT_TRUE(file2.good());
+ string text;
+ file2 >> text;
+ file2.close();
+
+ EXPECT_EQ(srvid_text, text);
+}
+
} // end of anonymous namespace
diff --git a/src/bin/dhcp4/tests/dhcp4_unittests.cc b/src/bin/dhcp4/tests/dhcp4_unittests.cc
index 0ed61b7..93e836b 100644
--- a/src/bin/dhcp4/tests/dhcp4_unittests.cc
+++ b/src/bin/dhcp4/tests/dhcp4_unittests.cc
@@ -13,13 +13,16 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <log/logger_support.h>
-
+#include <dhcp4/dhcp4_log.h>
#include <gtest/gtest.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
+
+ // See the documentation of the B10_* environment variables in
+ // src/lib/log/README for info on how to tweak logging
isc::log::initLogger();
int result = RUN_ALL_TESTS();
diff --git a/src/bin/dhcp6/config_parser.cc b/src/bin/dhcp6/config_parser.cc
index 944bb21..f2eb34c 100644
--- a/src/bin/dhcp6/config_parser.cc
+++ b/src/bin/dhcp6/config_parser.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -18,12 +18,15 @@
#include <dhcp/libdhcp++.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_log.h>
+#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/triplet.h>
#include <log/logger_support.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
#include <boost/algorithm/string.hpp>
#include <boost/foreach.hpp>
@@ -38,37 +41,56 @@
#include <stdint.h>
using namespace std;
+using namespace isc;
using namespace isc::data;
+using namespace isc::dhcp;
using namespace isc::asiolink;
-namespace isc {
-namespace dhcp {
+namespace {
+
+// Forward declarations of some of the parser classes.
+// They are used to define pointer types for these classes.
+class BooleanParser;
+class StringParser;
+class Uint32Parser;
-/// @brief an auxiliary type used for storing an element name and its parser
+// Pointers to various parser objects.
+typedef boost::shared_ptr<BooleanParser> BooleanParserPtr;
+typedef boost::shared_ptr<StringParser> StringParserPtr;
+typedef boost::shared_ptr<Uint32Parser> Uint32ParserPtr;
+
+/// @brief Auxiliary type used for storing an element name and its parser.
typedef pair<string, ConstElementPtr> ConfigPair;
-/// @brief a factory method that will create a parser for a given element name
-typedef DhcpConfigParser* ParserFactory(const std::string& config_id);
+/// @brief Factory method that will create a parser for a given element name
+typedef isc::dhcp::DhcpConfigParser* ParserFactory(const std::string& config_id);
-/// @brief a collection of factories that create parsers for specified element names
+/// @brief Collection of factories that create parsers for specified element names
typedef std::map<std::string, ParserFactory*> FactoryMap;
-/// @brief a collection of elements that store uint32 values (e.g. renew-timer = 900)
+/// @brief Storage for parsed boolean values.
+typedef std::map<string, bool> BooleanStorage;
+
+/// @brief Collection of elements that store uint32 values (e.g. renew-timer = 900).
typedef std::map<string, uint32_t> Uint32Storage;
-/// @brief a collection of elements that store string values
+/// @brief Collection of elements that store string values.
typedef std::map<string, string> StringStorage;
-/// @brief a collection of pools
+/// @brief Storage for option definitions.
+typedef OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> OptionDefStorage;
+
+/// @brief Collection of address pools.
///
/// This type is used as intermediate storage, when pools are parsed, but there is
/// no subnet object created yet to store them.
-typedef std::vector<Pool6Ptr> PoolStorage;
+typedef std::vector<isc::dhcp::Pool6Ptr> PoolStorage;
-/// @brief Collection of option descriptors. This container allows searching for
-/// options using the option code or persistency flag. This is useful when merging
-/// existing options with newly configured options.
-typedef Subnet::OptionContainer OptionStorage;
+/// Collection of containers holding option spaces. Each container within
+/// a particular option space holds so-called option descriptors.
+typedef OptionSpaceContainer<Subnet::OptionContainer,
+ Subnet::OptionDescriptor> OptionStorage;
/// @brief Global uint32 parameters that will be used as defaults.
Uint32Storage uint32_defaults;
@@ -79,6 +101,10 @@ StringStorage string_defaults;
/// @brief Global storage for options that will be used as defaults.
OptionStorage option_defaults;
+/// @brief Global storage for option definitions.
+OptionDefStorage option_def_intermediate;
+
+
/// @brief a dummy configuration parser
///
/// This is a debugging parser. It does not configure anything,
@@ -90,7 +116,7 @@ public:
/// @brief Constructor
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param param_name name of the parsed parameter
DebugParser(const std::string& param_name)
@@ -99,7 +125,7 @@ public:
/// @brief builds parameter value
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
///
/// @param new_config pointer to the new configuration
virtual void build(ConstElementPtr new_config) {
@@ -108,12 +134,12 @@ public:
value_ = new_config;
}
- /// @brief pretends to apply the configuration
+ /// @brief Pretends to apply the configuration.
///
/// This is a method required by the base class. It pretends to apply the
/// configuration, but in fact it only prints the parameter out.
///
- /// See \ref DhcpConfigParser class for details.
+ /// See @ref DhcpConfigParser class for details.
virtual void commit() {
// Debug message. The whole DebugParser class is used only for parser
// debugging, and is not used in production code. It is very convenient
@@ -125,7 +151,7 @@ public:
/// @brief factory that constructs DebugParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new DebugParser(param_name));
}
@@ -137,6 +163,86 @@ private:
ConstElementPtr value_;
};
+
+/// @brief A boolean value parser.
+///
+/// This parser handles configuration values of the boolean type.
+/// Parsed values are stored in a provided storage. If no storage
+/// is provided then the build function throws an exception.
+class BooleanParser : public DhcpConfigParser {
+public:
+ /// @brief Constructor.
+ ///
+ /// @param param_name name of the parameter.
+ BooleanParser(const std::string& param_name)
+ : storage_(NULL),
+ param_name_(param_name),
+ value_(false) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ }
+
+ /// @brief Parse a boolean value.
+ ///
+ /// @param value a value to be parsed.
+ ///
+ /// @throw isc::InvalidOperation if a storage has not been set
+ /// prior to calling this function
+ /// @throw isc::dhcp::DhcpConfigError if a provided parameter's
+ /// name is empty.
+ virtual void build(ConstElementPtr value) {
+ if (storage_ == NULL) {
+ isc_throw(isc::InvalidOperation, "parser logic error:"
+ << " storage for the " << param_name_
+ << " value has not been set");
+ } else if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+ // The Config Manager checks if user specified a
+ // valid value for a boolean parameter: True or False.
+ // It is then ok to assume that if str() does not return
+ // 'true' the value is 'false'.
+ value_ = (value->str() == "true") ? true : false;
+ }
+
+ /// @brief Put a parsed value to the storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ (*storage_)[param_name_] = value_;
+ }
+ }
+
+ /// @brief Create an instance of the boolean parser.
+ ///
+ /// @param param_name name of the parameter for which the
+ /// parser is created.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new BooleanParser(param_name));
+ }
+
+ /// @brief Set the storage for parsed value.
+ ///
+ /// This function must be called prior to calling build.
+ ///
+ /// @param storage a pointer to the storage where parsed data
+ /// is to be stored.
+ void setStorage(BooleanStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+ /// Pointer to the storage where parsed value is stored.
+ BooleanStorage* storage_;
+ /// Name of the parameter which value is parsed with this parser.
+ std::string param_name_;
+ /// Parsed value.
+ bool value_;
+};
+
/// @brief Configuration parser for uint32 parameters
///
/// This class is a generic parser that is able to handle any uint32 integer
@@ -144,10 +250,10 @@ private:
/// (uint32_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
///
/// @todo this class should be turned into the template class which
/// will handle all uintX_types of data (see ticket #2415).
@@ -155,18 +261,29 @@ class Uint32Parser : public DhcpConfigParser {
public:
/// @brief constructor for Uint32Parser
+ ///
/// @param param_name name of the configuration parameter being parsed
Uint32Parser(const std::string& param_name)
- :storage_(&uint32_defaults), param_name_(param_name) {
+ : storage_(&uint32_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
- /// @brief builds parameter value
- ///
- /// Parses configuration entry and stores it in a storage. See
- /// \ref Uint32Parser::setStorage() for details.
+ /// @brief Parses configuration configuration parameter as uint32_t.
///
/// @param value pointer to the content of parsed values
+ /// @throw isc::dhcp::DhcpConfigError if failed to parse
+ /// the configuration parameter as uint32_t value.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
+
bool parse_error = false;
// Cast the provided value to int64 value to check.
int64_t int64value = 0;
@@ -180,51 +297,45 @@ public:
parse_error = true;
}
if (!parse_error) {
+ // Check that the value is not out of bounds.
if ((int64value < 0) ||
(int64value > std::numeric_limits<uint32_t>::max())) {
parse_error = true;
} else {
- try {
- value_ = boost::lexical_cast<uint32_t>(value->str());
- } catch (const boost::bad_lexical_cast &) {
- parse_error = true;
- }
+ // A value is not out of bounds so let's cast it to
+ // the uint32_t type.
+ value_ = static_cast<uint32_t>(int64value);
}
}
-
+ // Invalid value provided.
if (parse_error) {
- isc_throw(BadValue, "Failed to parse value " << value->str()
+ isc_throw(isc::dhcp::DhcpConfigError, "Failed to parse value " << value->str()
<< " as unsigned 32-bit integer.");
}
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by \ref Subnet6Parser when constructing
- /// the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed uint32_t value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs Uint32Parser objects
+ /// @brief Factory that constructs Uint32Parser objects.
///
- /// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ /// @param param_name name of the parameter to be parsed.
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Uint32Parser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
- /// @param storage pointer to the storage container
+ /// @param storage pointer to the storage container.
void setStorage(Uint32Storage* storage) {
storage_ = storage;
}
@@ -232,10 +343,8 @@ public:
private:
/// pointer to the storage, where parsed value will be stored
Uint32Storage* storage_;
-
/// name of the parameter to be parsed
std::string param_name_;
-
/// the actual parsed value
uint32_t value_;
};
@@ -247,54 +356,60 @@ private:
/// (string_defaults). If used in smaller scopes (e.g. to parse parameters
/// in subnet config), it can be pointed to a different storage, using the
/// setStorage() method. This class follows the parser interface, laid out
-/// in its base class, \ref DhcpConfigParser.
+/// in its base class, @ref DhcpConfigParser.
///
/// For overview of usability of this generic purpose parser, see
-/// \ref dhcpv6ConfigInherit page.
+/// @ref dhcpv6ConfigInherit page.
class StringParser : public DhcpConfigParser {
public:
/// @brief constructor for StringParser
+ ///
/// @param param_name name of the configuration parameter being parsed
StringParser(const std::string& param_name)
- :storage_(&string_defaults), param_name_(param_name) {
+ : storage_(&string_defaults),
+ param_name_(param_name) {
+ // Empty parameter name is invalid.
+ if (param_name_.empty()) {
+ isc_throw(DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
}
/// @brief parses parameter value
///
- /// Parses configuration entry and stores it in storage. See
- /// \ref setStorage() for details.
+ /// Parses configuration parameter's value as string.
///
/// @param value pointer to the content of parsed values
+ /// @throws DhcpConfigError if the parsed parameter's name is empty.
virtual void build(ConstElementPtr value) {
+ if (param_name_.empty()) {
+ isc_throw(isc::dhcp::DhcpConfigError, "parser logic error:"
+ << "empty parameter name provided");
+ }
value_ = value->str();
boost::erase_all(value_, "\"");
-
- // If a given parameter already exists in the storage we override
- // its value. If it doesn't we insert a new element.
- (*storage_)[param_name_] = value_;
}
- /// @brief does nothing
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers are expected to
- /// use values stored in the storage, e.g. renew-timer for a given
- /// subnet is stored in subnet-specific storage. It is not commited
- /// here, but is rather used by its parent parser when constructing
- /// an object, e.g. the subnet.
- virtual void commit() { }
+ /// @brief Stores the parsed value in a storage.
+ virtual void commit() {
+ if (storage_ != NULL && !param_name_.empty()) {
+ // If a given parameter already exists in the storage we override
+ // its value. If it doesn't we insert a new element.
+ (*storage_)[param_name_] = value_;
+ }
+ }
- /// @brief factory that constructs StringParser objects
+ /// @brief Factory that constructs StringParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new StringParser(param_name));
}
- /// @brief sets storage for value of this parameter
+ /// @brief Sets storage for value of this parameter.
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(StringStorage* storage) {
@@ -302,17 +417,14 @@ public:
}
private:
- /// pointer to the storage, where parsed value will be stored
+ /// Pointer to the storage, where parsed value will be stored
StringStorage* storage_;
-
- /// name of the parameter to be parsed
+ /// Name of the parameter to be parsed
std::string param_name_;
-
- /// the actual parsed value
+ /// The actual parsed value
std::string value_;
};
-
/// @brief parser for interface list definition
///
/// This parser handles Dhcp6/interface entry.
@@ -333,7 +445,7 @@ public:
/// @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 "
+ isc_throw(isc::BadValue, "Internal error. Interface configuration "
"parser called for the wrong parameter: " << param_name);
}
}
@@ -359,7 +471,7 @@ public:
/// @brief factory that constructs InterfaceListConfigParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new InterfaceListConfigParser(param_name));
}
@@ -383,7 +495,7 @@ public:
/// @brief constructor.
PoolParser(const std::string& /*param_name*/)
- :pools_(NULL) {
+ : pools_(NULL) {
// ignore parameter name, it is always Dhcp6/subnet6[X]/pool
}
@@ -393,11 +505,13 @@ public:
/// 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)
+ /// @throw isc::InvalidOperation if storage was not specified
+ /// (setStorage() not called)
void build(ConstElementPtr pools_list) {
+
// setStorage() should have been called before build
if (!pools_) {
- isc_throw(NotImplemented, "Parser logic error. No pool storage set,"
+ isc_throw(isc::InvalidOperation, "parser logic error: no pool storage set,"
" but pool parser asked to parse pools");
}
@@ -433,12 +547,12 @@ public:
// be checked in Pool6 constructor.
len = boost::lexical_cast<int>(prefix_len);
} catch (...) {
- isc_throw(Dhcp6ConfigError, "Failed to parse pool "
+ isc_throw(DhcpConfigError, "failed to parse pool "
"definition: " << text_pool->stringValue());
}
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, addr, len));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
@@ -451,11 +565,11 @@ public:
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, min, max));
- pools_->push_back(pool);
+ local_pools_.push_back(pool);
continue;
}
- isc_throw(Dhcp6ConfigError, "Failed to parse pool definition:"
+ isc_throw(DhcpConfigError, "failed to parse pool definition:"
<< text_pool->stringValue() <<
". Does not contain - (for min-max) nor / (prefix/len)");
}
@@ -463,24 +577,29 @@ public:
/// @brief sets storage for value of this parameter
///
- /// See \ref dhcpv6ConfigInherit for details.
+ /// See @ref dhcpv6ConfigInherit for details.
///
/// @param storage pointer to the storage container
void setStorage(PoolStorage* storage) {
pools_ = storage;
}
- /// @brief does nothing.
- ///
- /// This method is required for all parsers. The value itself
- /// is not commited anywhere. Higher level parsers (for subnet) are expected
- /// to use values stored in the storage.
- virtual void commit() {}
+ /// @brief Stores the parsed values in a storage provided
+ /// by an upper level parser.
+ virtual void commit() {
+ if (pools_) {
+ // local_pools_ holds the values produced by the build function.
+ // At this point parsing should have completed successfuly so
+ // we can append new data to the supplied storage.
+ pools_->insert(pools_->end(), local_pools_.begin(),
+ local_pools_.end());
+ }
+ }
/// @brief factory that constructs PoolParser objects
///
/// @param param_name name of the parameter to be parsed
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new PoolParser(param_name));
}
@@ -490,21 +609,30 @@ private:
/// This is typically a storage somewhere in Subnet parser
/// (an upper level parser).
PoolStorage* pools_;
+ /// A temporary storage for pools configuration. It is a
+ /// storage where pools are stored by build function.
+ PoolStorage local_pools_;
};
+
/// @brief Parser for option data value.
///
/// This parser parses configuration entries that specify value of
/// a single option. These entries include option name, option code
-/// and data carried by the option. If parsing is successful then an
-/// instance of an option is created and added to the storage provided
-/// by the calling class.
-///
-/// @todo This class parses and validates the option name. However it is
-/// not used anywhere until support for option spaces is implemented
-/// (see tickets #2319, #2314). When option spaces are implemented
-/// there will be a way to reference the particular option using
-/// its type (code) or option name.
+/// and data carried by the option. The option data can be specified
+/// in one of the two available formats: binary value represented as
+/// a string of hexadecimal digits or a list of comma separated values.
+/// The format being used is controlled by csv-format configuration
+/// parameter. When setting this value to True, the latter format is
+/// used. The subsequent values in the CSV format apply to relevant
+/// option data fields in the configured option. For example the
+/// configuration: "data" : "192.168.2.0, 56, hello world" can be
+/// used to set values for the option comprising IPv4 address,
+/// integer and string data field. Note that order matters. If the
+/// order of values does not match the order of data fields within
+/// an option the configuration will not be accepted. If parsing
+/// is successful then an instance of an option is created and
+/// added to the storage provided by the calling class.
class OptionDataParser : public DhcpConfigParser {
public:
@@ -528,45 +656,53 @@ public:
///
/// @param option_data_entries collection of entries that define value
/// for a particular option.
- /// @throw Dhcp6ConfigError if invalid parameter specified in
+ /// @throw DhcpConfigError if invalid parameter specified in
/// the configuration.
/// @throw isc::InvalidOperation if failed to set storage prior to
/// calling build.
- /// @throw isc::BadValue if option data storage is invalid.
virtual void build(ConstElementPtr option_data_entries) {
+
if (options_ == NULL) {
isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
"parsing option data.");
}
BOOST_FOREACH(ConfigPair param, option_data_entries->mapValue()) {
ParserPtr parser;
- if (param.first == "name") {
+ if (param.first == "name" || param.first == "data" ||
+ param.first == "space") {
boost::shared_ptr<StringParser>
- name_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ name_parser(dynamic_cast<StringParser*>(StringParser::factory(param.first)));
if (name_parser) {
name_parser->setStorage(&string_values_);
parser = name_parser;
}
} else if (param.first == "code") {
boost::shared_ptr<Uint32Parser>
- code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::Factory(param.first)));
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(param.first)));
if (code_parser) {
code_parser->setStorage(&uint32_values_);
parser = code_parser;
}
- } else if (param.first == "data") {
- boost::shared_ptr<StringParser>
- value_parser(dynamic_cast<StringParser*>(StringParser::Factory(param.first)));
+ } else if (param.first == "csv-format") {
+ boost::shared_ptr<BooleanParser>
+ value_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(param.first)));
if (value_parser) {
- value_parser->setStorage(&string_values_);
+ value_parser->setStorage(&boolean_values_);
parser = value_parser;
}
} else {
- isc_throw(Dhcp6ConfigError,
- "Parser error: option-data parameter not supported: "
+ isc_throw(DhcpConfigError,
+ "parser error: option-data parameter not supported: "
<< param.first);
}
parser->build(param.second);
+ // Before we can create an option we need to get the data from
+ // the child parsers. The only way to do it is to invoke commit
+ // on them so as they store the values in appropriate storages
+ // that this class provided to them. Note that this will not
+ // modify values stored in the global storages so the configuration
+ // will remain consistent even parsing fails somewhere further on.
+ parser->commit();
}
// Try to create the option instance.
createOption();
@@ -582,16 +718,21 @@ public:
/// remain un-modified.
virtual void commit() {
if (options_ == NULL) {
- isc_throw(isc::InvalidOperation, "Parser logic error: storage must be set before "
+ isc_throw(isc::InvalidOperation, "parser logic error: storage must be set before "
"commiting option data.");
} else if (!option_descriptor_.option) {
// Before we can commit the new option should be configured. If it is not
// than somebody must have called commit() before build().
- isc_throw(isc::InvalidOperation, "Parser logic error: no option has been configured and"
+ isc_throw(isc::InvalidOperation, "parser logic error: no option has been configured and"
" thus there is nothing to commit. Has build() been called?");
}
uint16_t opt_type = option_descriptor_.option->getType();
- Subnet::OptionContainerTypeIndex& idx = options_->get<1>();
+ Subnet::OptionContainerPtr options = options_->getItems(option_space_);
+ // The getItems() should never return NULL pointer. If there are no
+ // options configured for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(options);
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to find options with the particular option code in the main
// storage. If found, remove these options because they will be
// replaced with new one.
@@ -601,7 +742,7 @@ public:
idx.erase(range.first, range.second);
}
// Append new option to the main storage.
- options_->push_back(option_descriptor_);
+ options_->addItem(option_descriptor_, option_space_);
}
/// @brief Set storage for the parser.
@@ -629,63 +770,103 @@ private:
/// is intitialized but this check is not needed here because it is done
/// in the \ref build function.
///
- /// @throw Dhcp6ConfigError if parameters provided in the configuration
+ /// @throw DhcpConfigError if parameters provided in the configuration
/// are invalid.
void createOption() {
+
// Option code is held in the uint32_t storage but is supposed to
// be uint16_t value. We need to check that value in the configuration
// does not exceed range of uint16_t and is not zero.
- uint32_t option_code = getUint32Param("code");
+ uint32_t option_code = getParam<uint32_t>("code", uint32_values_);
if (option_code == 0) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " be equal to zero. Option code '0' is reserved in"
<< " DHCPv6.");
} else if (option_code > std::numeric_limits<uint16_t>::max()) {
- isc_throw(Dhcp6ConfigError, "Parser error: value of 'code' must not"
+ isc_throw(DhcpConfigError, "Parser error: value of 'code' must not"
<< " exceed " << std::numeric_limits<uint16_t>::max());
}
// Check that the option name has been specified, is non-empty and does not
// contain spaces.
// @todo possibly some more restrictions apply here?
- std::string option_name = getStringParam("name");
+ std::string option_name = getParam<std::string>("name", string_values_);
if (option_name.empty()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not be"
+ isc_throw(DhcpConfigError, "Parser error: option name must not be"
<< " empty");
} else if (option_name.find(" ") != std::string::npos) {
- isc_throw(Dhcp6ConfigError, "Parser error: option name must not contain"
+ isc_throw(DhcpConfigError, "Parser error: option name must not contain"
<< " spaces");
}
+ std::string option_space = getParam<std::string>("space", string_values_);
+ /// @todo Validate option space once #2313 is merged.
+
+ OptionDefinitionPtr def;
+ if (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, option_code)) {
+ def = LibDHCP::getOptionDef(Option::V6, option_code);
+
+ } else if (option_space == "dhcp4") {
+ isc_throw(DhcpConfigError, "'dhcp4' option space name is reserved"
+ << " for DHCPv4 server");
+ } else {
+ // If we are not dealing with a standard option then we
+ // need to search for its definition among user-configured
+ // options. They are expected to be in the global storage
+ // already.
+ OptionDefContainerPtr defs = option_def_intermediate.getItems(option_space);
+ // The getItems() should never return the NULL pointer. If there are
+ // no option definitions for the particular option space a pointer
+ // to an empty container should be returned.
+ assert(defs);
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ OptionDefContainerTypeRange range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) > 0) {
+ def = *range.first;
+ }
+ if (!def) {
+ isc_throw(DhcpConfigError, "definition for the option '"
+ << option_space << "." << option_name
+ << "' having code '" << option_code
+ << "' does not exist");
+ }
+
+ }
+
// Get option data from the configuration database ('data' field).
- // Option data is specified by the user as case insensitive string
- // of hexadecimal digits for each option.
- std::string option_data = getStringParam("data");
+ const std::string option_data = getParam<std::string>("data", string_values_);
+ const bool csv_format = getParam<bool>("csv-format", boolean_values_);
+
// Transform string of hexadecimal digits into binary format.
std::vector<uint8_t> binary;
- try {
- util::encode::decodeHex(option_data, binary);
- } catch (...) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data is not a valid"
- << " string of hexadecimal digits: " << option_data);
+ std::vector<std::string> data_tokens;
+
+ if (csv_format) {
+ // If the option data is specified as a string of comma
+ // separated values then we need to split this string into
+ // individual values - each value will be used to initialize
+ // one data field of an option.
+ data_tokens = isc::util::str::tokens(option_data, ",");
+ } else {
+ // Otherwise, the option data is specified as a string of
+ // hexadecimal digits that we have to turn into binary format.
+ try {
+ util::encode::decodeHex(option_data, binary);
+ } catch (...) {
+ isc_throw(DhcpConfigError, "Parser error: option data is not a valid"
+ << " string of hexadecimal digits: " << option_data);
+ }
}
- // Get all existing DHCPv6 option definitions. The one that matches
- // our option will be picked and used to create it.
- OptionDefContainer option_defs = LibDHCP::getOptionDefs(Option::V6);
- // Get search index #1. It allows searching for options definitions
- // using option type value.
- const OptionDefContainerTypeIndex& idx = option_defs.get<1>();
- // Get all option definitions matching option code we want to create.
- const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
- size_t num_defs = std::distance(range.first, range.second);
+
OptionPtr option;
- // Currently we do not allow duplicated definitions and if there are
- // any duplicates we issue internal server error.
- if (num_defs > 1) {
- isc_throw(Dhcp6ConfigError, "Internal error: currently it is not"
- << " supported to initialize multiple option definitions"
- << " for the same option code. This will be supported once"
- << " there option spaces are implemented.");
- } else if (num_defs == 0) {
+ if (!def) {
+ if (csv_format) {
+ isc_throw(DhcpConfigError, "the CSV option data format can be"
+ " used to specify values for an option that has a"
+ " definition. The option with code " << option_code
+ << " does not have a definition.");
+ }
+
// @todo We have a limited set of option definitions intiialized at the moment.
// In the future we want to initialize option definitions for all options.
// Consequently an error will be issued if an option definition does not exist
@@ -699,57 +880,52 @@ private:
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} else {
- // We have exactly one option definition for the particular option code
- // use it to create the option instance.
- const OptionDefinitionPtr& def = *(range.first);
+
+ // Option name should match the definition. The option name
+ // may seem to be redundant but in the future we may want
+ // to reference options and definitions using their names
+ // and/or option codes so keeping the option name in the
+ // definition of option value makes sense.
+ if (def->getName() != option_name) {
+ isc_throw(DhcpConfigError, "specified option name '"
+ << option_name << " does not match the "
+ << "option definition: '" << option_space
+ << "." << def->getName() << "'");
+ }
+
+ // Option definition has been found so let's use it to create
+ // an instance of our option.
try {
- OptionPtr option = def->optionFactory(Option::V6, option_code, binary);
+ OptionPtr option = csv_format ?
+ def->optionFactory(Option::V6, option_code, data_tokens) :
+ def->optionFactory(Option::V6, option_code, binary);
Subnet::OptionDescriptor desc(option, false);
option_descriptor_.option = option;
option_descriptor_.persistent = false;
} catch (const isc::Exception& ex) {
- isc_throw(Dhcp6ConfigError, "Parser error: option data does not match"
- << " option definition (code " << option_code << "): "
+ isc_throw(DhcpConfigError, "option data does not match"
+ << " option definition (space: " << option_space
+ << ", code: " << option_code << "): "
<< ex.what());
}
}
- }
-
- /// @brief Get a parameter from the strings storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- std::string getStringParam(const std::string& param_id) const {
- StringStorage::const_iterator param = string_values_.find(param_id);
- if (param == string_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
- }
-
- /// @brief Get a parameter from the uint32 values storage.
- ///
- /// @param param_id parameter identifier.
- /// @throw Dhcp6ConfigError if parameter has not been found.
- uint32_t getUint32Param(const std::string& param_id) const {
- Uint32Storage::const_iterator param = uint32_values_.find(param_id);
- if (param == uint32_values_.end()) {
- isc_throw(Dhcp6ConfigError, "Parser error: option-data parameter"
- << " '" << param_id << "' not specified");
- }
- return (param->second);
+ // All went good, so we can set the option space name.
+ option_space_ = option_space;
}
/// Storage for uint32 values (e.g. option code).
Uint32Storage uint32_values_;
/// Storage for string values (e.g. option name or data).
StringStorage string_values_;
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
/// Pointer to options storage. This storage is provided by
/// the calling class and is shared by all OptionDataParser objects.
OptionStorage* options_;
/// Option descriptor holds newly configured option.
- Subnet::OptionDescriptor option_descriptor_;
+ isc::dhcp::Subnet::OptionDescriptor option_descriptor_;
+ /// Option space name where the option belongs to.
+ std::string option_space_;
};
/// @brief Parser for option data values within a subnet.
@@ -775,7 +951,7 @@ public:
/// for options within a single subnet and creates options' instances.
///
/// @param option_data_list pointer to a list of options' data sets.
- /// @throw Dhcp6ConfigError if option parsing failed.
+ /// @throw DhcpConfigError if option parsing failed.
void build(ConstElementPtr option_data_list) {
BOOST_FOREACH(ConstElementPtr option_value, option_data_list->listValue()) {
boost::shared_ptr<OptionDataParser> parser(new OptionDataParser("option-data"));
@@ -815,7 +991,7 @@ public:
/// @param param_name param name.
///
/// @return DhcpConfigParser object.
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new OptionDataListParser(param_name));
}
@@ -830,6 +1006,254 @@ public:
ParserCollection parsers_;
};
+/// @brief Parser for a single option definition.
+///
+/// This parser creates an instance of a single option definition.
+class OptionDefParser: DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor sets the pointer to the option definitions
+ /// storage to NULL. It must be set to point to the actual storage
+ /// before \ref build is called.
+ OptionDefParser(const std::string&)
+ : storage_(NULL) {
+ }
+
+ /// @brief Parses an entry that describes single option definition.
+ ///
+ /// @param option_def a configuration entry to be parsed.
+ ///
+ /// @throw DhcpConfigError if parsing was unsuccessful.
+ void build(ConstElementPtr option_def) {
+ if (storage_ == NULL) {
+ isc_throw(DhcpConfigError, "parser logic error: storage must be set"
+ " before parsing option definition data");
+ }
+ // Parse the elements that make up the option definition.
+ BOOST_FOREACH(ConfigPair param, option_def->mapValue()) {
+ std::string entry(param.first);
+ ParserPtr parser;
+ if (entry == "name" || entry == "type" ||
+ entry == "record-types" || entry == "space") {
+ StringParserPtr
+ str_parser(dynamic_cast<StringParser*>(StringParser::factory(entry)));
+ if (str_parser) {
+ str_parser->setStorage(&string_values_);
+ parser = str_parser;
+ }
+ } else if (entry == "code") {
+ Uint32ParserPtr
+ code_parser(dynamic_cast<Uint32Parser*>(Uint32Parser::factory(entry)));
+ if (code_parser) {
+ code_parser->setStorage(&uint32_values_);
+ parser = code_parser;
+ }
+ } else if (entry == "array") {
+ BooleanParserPtr
+ array_parser(dynamic_cast<BooleanParser*>(BooleanParser::factory(entry)));
+ if (array_parser) {
+ array_parser->setStorage(&boolean_values_);
+ parser = array_parser;
+ }
+ } else {
+ isc_throw(DhcpConfigError, "invalid parameter: " << entry);
+ }
+
+ parser->build(param.second);
+ parser->commit();
+ }
+
+ // Create an instance of option definition.
+ createOptionDef();
+
+ // Get all items we collected so far for the particular option space.
+ OptionDefContainerPtr defs = storage_->getItems(option_space_name_);
+ // Check if there are any items with option code the same as the
+ // one specified for the definition we are now creating.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range =
+ idx.equal_range(option_definition_->getCode());
+ // If there are any items with this option code already we need
+ // to issue an error because we don't allow duplicates for
+ // option definitions within an option space.
+ if (std::distance(range.first, range.second) > 0) {
+ isc_throw(DhcpConfigError, "duplicated option definition for"
+ << " code '" << option_definition_->getCode() << "'");
+ }
+ }
+
+ /// @brief Stores the parsed option definition in the data store.
+ void commit() {
+ // @todo validate option space name once 2313 is merged.
+ if (storage_ && option_definition_) {
+ storage_->addItem(option_definition_, option_space_name_);
+ }
+ }
+
+ /// @brief Sets a pointer to the data store.
+ ///
+ /// The newly created instance of an option definition will be
+ /// added to the data store given by the argument.
+ ///
+ /// @param storage pointer to the data store where the option definition
+ /// will be added to.
+ void setStorage(OptionDefStorage* storage) {
+ storage_ = storage;
+ }
+
+private:
+
+ /// @brief Create option definition from the parsed parameters.
+ void createOptionDef() {
+ // Get the option space name and validate it.
+ std::string space = getParam<std::string>("space", string_values_);
+ // @todo uncomment the code below when the #2313 is merged.
+ /* if (!OptionSpace::validateName()) {
+ isc_throw(DhcpConfigError, "invalid option space name '"
+ << space << "'");
+ } */
+
+ // Get other parameters that are needed to create the
+ // option definition.
+ std::string name = getParam<std::string>("name", string_values_);
+ uint32_t code = getParam<uint32_t>("code", uint32_values_);
+ std::string type = getParam<std::string>("type", string_values_);
+ bool array_type = getParam<bool>("array", boolean_values_);
+
+ OptionDefinitionPtr def(new OptionDefinition(name, code,
+ type, array_type));
+ // The record-types field may carry a list of comma separated names
+ // of data types that form a record.
+ std::string record_types = getParam<std::string>("record-types",
+ string_values_);
+ // Split the list of record types into tokens.
+ std::vector<std::string> record_tokens =
+ isc::util::str::tokens(record_types, ",");
+ // Iterate over each token and add a record type into
+ // option definition.
+ BOOST_FOREACH(std::string record_type, record_tokens) {
+ try {
+ boost::trim(record_type);
+ if (!record_type.empty()) {
+ def->addRecordField(record_type);
+ }
+ } catch (const Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid record type values"
+ << " specified for the option definition: "
+ << ex.what());
+ }
+ }
+
+ // Check the option definition parameters are valid.
+ try {
+ def->validate();
+ } catch (const isc::Exception& ex) {
+ isc_throw(DhcpConfigError, "invalid option definition"
+ << " parameters: " << ex.what());
+ }
+ // Option definition has been created successfully.
+ option_space_name_ = space;
+ option_definition_ = def;
+ }
+
+ /// Instance of option definition being created by this parser.
+ OptionDefinitionPtr option_definition_;
+ /// Name of the space the option definition belongs to.
+ std::string option_space_name_;
+
+ /// Pointer to a storage where the option definition will be
+ /// added when \ref commit is called.
+ OptionDefStorage* storage_;
+
+ /// Storage for boolean values.
+ BooleanStorage boolean_values_;
+ /// Storage for string values.
+ StringStorage string_values_;
+ /// Storage for uint32 values.
+ Uint32Storage uint32_values_;
+};
+
+/// @brief Parser for a list of option definitions.
+///
+/// This parser iterates over all configuration entries that define
+/// option definitions and creates instances of these definitions.
+/// If the parsing is successful, the collection of created definitions
+/// is put into the provided storage.
+class OptionDefListParser : DhcpConfigParser {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// This constructor initializes the pointer to option definitions
+ /// storage to NULL value. This pointer has to be set to point to
+ /// the actual storage before the \ref build function is called.
+ OptionDefListParser(const std::string&) {
+ }
+
+ /// @brief Parse configuration entries.
+ ///
+ /// This function parses configuration entries and creates instances
+ /// of option definitions.
+ ///
+ /// @param option_def_list pointer to an element that holds entries
+ /// that define option definitions.
+ /// @throw DhcpConfigError if configuration parsing fails.
+ void build(ConstElementPtr option_def_list) {
+ // Clear existing items in the global storage.
+ // We are going to replace all of them.
+ option_def_intermediate.clearItems();
+
+ if (!option_def_list) {
+ isc_throw(DhcpConfigError, "parser error: a pointer to a list of"
+ << " option definitions is NULL");
+ }
+
+ BOOST_FOREACH(ConstElementPtr option_def, option_def_list->listValue()) {
+ boost::shared_ptr<OptionDefParser>
+ parser(new OptionDefParser("single-option-def"));
+ parser->setStorage(&option_def_intermediate);
+ parser->build(option_def);
+ parser->commit();
+ }
+ }
+
+ /// @brief Stores option definitions in the CfgMgr.
+ void commit() {
+
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ cfg_mgr.deleteOptionDefs();
+
+ // We need to move option definitions from the temporary
+ // storage to the global storage.
+ std::list<std::string> space_names =
+ option_def_intermediate.getOptionSpaceNames();
+ BOOST_FOREACH(std::string space_name, space_names) {
+
+ BOOST_FOREACH(OptionDefinitionPtr def,
+ *option_def_intermediate.getItems(space_name)) {
+ // All option definitions should be initialized to non-NULL
+ // values. The validation is expected to be made by the
+ // OptionDefParser when creating an option definition.
+ assert(def);
+ cfg_mgr.addOptionDef(def, space_name);
+ }
+ }
+ }
+
+ /// @brief Create an OptionDefListParser object.
+ ///
+ /// @param param_name configuration entry holding option definitions.
+ ///
+ /// @return OptionDefListParser object.
+ static DhcpConfigParser* factory(const std::string& param_name) {
+ return (new OptionDefListParser(param_name));
+ }
+
+};
+
/// @brief this class parses a single subnet
///
/// This class parses the whole subnet definition. It creates parsers
@@ -846,6 +1270,8 @@ public:
/// @brief parses parameter value
///
/// @param subnet pointer to the content of subnet definition
+ ///
+ /// @throw isc::DhcpConfigError if subnet configuration parsing failed.
void build(ConstElementPtr subnet) {
BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
@@ -871,46 +1297,119 @@ public:
// Appropriate parsers are created in the createSubnet6ConfigParser
// and they should be limited to those that we check here for. Thus,
// if we fail to find a matching parser here it is a programming error.
- isc_throw(Dhcp6ConfigError, "failed to find suitable parser");
+ isc_throw(DhcpConfigError, "failed to find suitable parser");
}
}
- // Ok, we now have subnet parsed
- }
- /// @brief commits received configuration.
- ///
- /// This method does most of the configuration. Many other parsers are just
- /// storing the values that are actually consumed here. Pool definitions
- /// created in other parsers are used here and added to newly created Subnet6
- /// objects. Subnet6 are then added to DHCP CfgMgr.
- void commit() {
- // Invoke commit on all sub-data parsers.
+ // In order to create new subnet we need to get the data out
+ // of the child parsers first. The only way to do it is to
+ // invoke commit on them because it will make them write
+ // parsed data into storages we have supplied.
+ // Note that triggering commits on child parsers does not
+ // affect global data because we supplied pointers to storages
+ // local to this object. Thus, even if this method fails
+ // later on, the configuration remains consistent.
BOOST_FOREACH(ParserPtr parser, parsers_) {
parser->commit();
}
+ // Create a subnet.
+ createSubnet();
+ }
+
+ /// @brief Adds the created subnet to a server's configuration.
+ void commit() {
+ if (subnet_) {
+ isc::dhcp::CfgMgr::instance().addSubnet6(subnet_);
+ }
+ }
+
+private:
+
+ /// @brief Set storage for a parser and invoke build.
+ ///
+ /// This helper method casts the provided parser pointer to the specified
+ /// type. If the cast is successful it sets the corresponding storage for
+ /// this parser, invokes build on it and saves the parser.
+ ///
+ /// @tparam T parser type to which parser argument should be cast.
+ /// @tparam Y storage type for the specified parser type.
+ /// @param parser parser on which build must be invoked.
+ /// @param storage reference to a storage that will be set for a parser.
+ /// @param subnet subnet element read from the configuration and being parsed.
+ /// @return true if parser pointer was successfully cast to specialized
+ /// parser type provided as Y.
+ template<typename T, typename Y>
+ bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
+ // We need to cast to T in order to set storage for the parser.
+ boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
+ // It is common that this cast is not successful because we try to cast to all
+ // supported parser types as we don't know the type of a parser in advance.
+ if (cast_parser) {
+ // Cast, successful so we go ahead with setting storage and actual parse.
+ cast_parser->setStorage(&storage);
+ parser->build(subnet);
+ parsers_.push_back(parser);
+ // We indicate that cast was successful so as the calling function
+ // may skip attempts to cast to other parser types and proceed to
+ // next element.
+ return (true);
+ }
+ // It was not successful. Indicate that another parser type
+ // should be tried.
+ return (false);
+ }
+
+ /// @brief Create a new subnet using a data from child parsers.
+ ///
+ /// @throw isc::dhcp::DhcpConfigError if subnet configuration parsing failed.
+ void createSubnet() {
+
+ // Find a subnet string.
StringStorage::const_iterator it = string_values_.find("subnet");
if (it == string_values_.end()) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Mandatory subnet definition in subnet missing");
}
+ // Remove any spaces or tabs.
string subnet_txt = it->second;
boost::erase_all(subnet_txt, " ");
boost::erase_all(subnet_txt, "\t");
-
+ // The subnet format is prefix/len. We are going to extract
+ // the prefix portion of a subnet string to create IOAddress
+ // object from it. IOAddress will be passed to the Subnet's
+ // constructor later on. In order to extract the prefix we
+ // need to get all characters preceding "/".
size_t pos = subnet_txt.find("/");
if (pos == string::npos) {
- isc_throw(Dhcp6ConfigError,
+ isc_throw(DhcpConfigError,
"Invalid subnet syntax (prefix/len expected):" << it->second);
}
+
+ // Try to create the address object. It also validates that
+ // the address syntax is ok.
IOAddress addr(subnet_txt.substr(0, pos));
uint8_t len = boost::lexical_cast<unsigned int>(subnet_txt.substr(pos + 1));
+ // Get all 'time' parameters using inheritance.
+ // If the subnet-specific value is defined then use it, else
+ // use the global value. The global value must always be
+ // present. If it is not, it is an internal error and exception
+ // is thrown.
Triplet<uint32_t> t1 = getParam("renew-timer");
Triplet<uint32_t> t2 = getParam("rebind-timer");
Triplet<uint32_t> pref = getParam("preferred-lifetime");
Triplet<uint32_t> valid = getParam("valid-lifetime");
+ // Get interface name. If it is defined, then the subnet is available
+ // directly over specified network interface.
+
+ string iface;
+ StringStorage::const_iterator iface_iter = string_values_.find("interface");
+ if (iface_iter != string_values_.end()) {
+ iface = iface_iter->second;
+ }
+
/// @todo: Convert this to logger once the parser is working reliably
stringstream tmp;
tmp << addr.toText() << "/" << (int)len
@@ -919,127 +1418,113 @@ public:
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_NEW_SUBNET).arg(tmp.str());
- Subnet6Ptr subnet(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Create a new subnet.
+ subnet_.reset(new Subnet6(addr, len, t1, t2, pref, valid));
+ // Add pools to it.
for (PoolStorage::iterator it = pools_.begin(); it != pools_.end(); ++it) {
- subnet->addPool6(*it);
+ subnet_->addPool(*it);
}
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ // Configure interface, if defined
+ if (!iface.empty()) {
+ if (!IfaceMgr::instance().getIface(iface)) {
+ isc_throw(DhcpConfigError, "Specified interface name " << iface
+ << " for subnet " << subnet_->toText() << " is not present"
+ << " in the system.");
+ }
- // Add subnet specific options.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, options_) {
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- if (std::distance(range.first, range.second) > 0) {
- LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
- .arg(desc.option->getType()).arg(addr.toText());
+ subnet_->setIface(iface);
+ }
+
+ // We are going to move configured options to the Subnet object.
+ // Configured options reside in the container where options
+ // are grouped by space names. Thus we need to get all space names
+ // and iterate over all options that belong to them.
+ std::list<std::string> space_names = options_.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all options within a particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *options_.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // We want to check whether an option with the particular
+ // option code has been already added. If so, we want
+ // to issue a warning.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor("option_space",
+ desc.option->getType());
+ if (existing_desc.option) {
+ LOG_WARN(dhcp6_logger, DHCP6_CONFIG_OPTION_DUPLICATE)
+ .arg(desc.option->getType()).arg(addr.toText());
+ }
+ // In any case, we add the option to the subnet.
+ subnet_->addOption(desc.option, false, option_space);
}
- subnet->addOption(desc.option);
}
// Check all global options and add them to the subnet object if
// they have been configured in the global scope. If they have been
// configured in the subnet scope we don't add global option because
// the one configured in the subnet scope always takes precedence.
- BOOST_FOREACH(Subnet::OptionDescriptor desc, option_defaults) {
- // Get all options specified locally in the subnet and having
- // code equal to global option's code.
- Subnet::OptionContainerTypeRange range = idx.equal_range(desc.option->getType());
- // @todo: In the future we will be searching for options using either
- // an option code or namespace. Currently we have only the option
- // code available so if there is at least one option found with the
- // specific code we don't add the globally configured option.
- // @todo with this code the first globally configured option
- // with the given code will be added to a subnet. We may
- // want to issue a warning about dropping the configuration of
- // a global option if one already exsists.
- if (std::distance(range.first, range.second) == 0) {
- subnet->addOption(desc.option);
+ space_names = option_defaults.getOptionSpaceNames();
+ BOOST_FOREACH(std::string option_space, space_names) {
+ // Get all global options for the particular option space.
+ BOOST_FOREACH(Subnet::OptionDescriptor desc,
+ *option_defaults.getItems(option_space)) {
+ // The pointer should be non-NULL. The validation is expected
+ // to be performed by the OptionDataParser before adding an
+ // option descriptor to the container.
+ assert(desc.option);
+ // Check if the particular option has been already added.
+ // This would mean that it has been configured in the
+ // subnet scope. Since option values configured in the
+ // subnet scope take precedence over globally configured
+ // values we don't add option from the global storage
+ // if there is one already.
+ Subnet::OptionDescriptor existing_desc =
+ subnet_->getOptionDescriptor(option_space, desc.option->getType());
+ if (!existing_desc.option) {
+ subnet_->addOption(desc.option, false, option_space);
+ }
}
}
-
- CfgMgr::instance().addSubnet6(subnet);
- }
-
-private:
-
- /// @brief Set storage for a parser and invoke build.
- ///
- /// This helper method casts the provided parser pointer to the specified
- /// type. If the cast is successful it sets the corresponding storage for
- /// this parser, invokes build on it and saves the parser.
- ///
- /// @tparam T parser type to which parser argument should be cast.
- /// @tparam Y storage type for the specified parser type.
- /// @param parser parser on which build must be invoked.
- /// @param storage reference to a storage that will be set for a parser.
- /// @param subnet subnet element read from the configuration and being parsed.
- /// @return true if parser pointer was successfully cast to specialized
- /// parser type provided as Y.
- template<typename T, typename Y>
- bool buildParser(const ParserPtr& parser, Y& storage, const ConstElementPtr& subnet) {
- // We need to cast to T in order to set storage for the parser.
- boost::shared_ptr<T> cast_parser = boost::dynamic_pointer_cast<T>(parser);
- // It is common that this cast is not successful because we try to cast to all
- // supported parser types as we don't know the type of a parser in advance.
- if (cast_parser) {
- // Cast, successful so we go ahead with setting storage and actual parse.
- cast_parser->setStorage(&storage);
- parser->build(subnet);
- parsers_.push_back(parser);
- // We indicate that cast was successful so as the calling function
- // may skip attempts to cast to other parser types and proceed to
- // next element.
- return (true);
- }
- // It was not successful. Indicate that another parser type
- // should be tried.
- return (false);
}
/// @brief creates parsers for entries in subnet definition
///
- /// @todo Add subnet-specific things here (e.g. subnet-specific options)
- ///
/// @param config_id name od the entry
+ ///
/// @return parser object for specified entry name
- /// @throw NotImplemented if trying to create a parser for unknown config element
+ /// @throw isc::dhcp::DhcpConfigError if trying to create a parser
+ /// for unknown config element
DhcpConfigParser* createSubnet6ConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "subnet", StringParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "pool", PoolParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["subnet"] = StringParser::factory;
+ factories["pool"] = PoolParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["interface"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
// Used for debugging only.
// return new DebugParser(config_id);
- isc_throw(NotImplemented,
- "Parser error: Subnet6 parameter not supported: "
+ isc_throw(isc::dhcp::DhcpConfigError,
+ "parser error: subnet6 parameter not supported: "
<< config_id);
}
return (f->second(config_id));
}
- /// @brief returns value for a given parameter (after using inheritance)
+ /// @brief Returns value for a given parameter (after using inheritance)
///
/// This method implements inheritance. For a given parameter name, it first
/// checks if there is a global value for it and overwrites it with specific
@@ -1047,8 +1532,8 @@ 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) {
+ /// @throw DhcpConfigError when requested parameter is not present
+ isc::dhcp::Triplet<uint32_t> getParam(const std::string& name) {
uint32_t value = 0;
bool found = false;
Uint32Storage::iterator global = uint32_defaults.find(name);
@@ -1064,9 +1549,9 @@ private:
}
if (found) {
- return (Triplet<uint32_t>(value));
+ return (isc::dhcp::Triplet<uint32_t>(value));
} else {
- isc_throw(Dhcp6ConfigError, "Mandatory parameter " << name
+ isc_throw(isc::dhcp::DhcpConfigError, "Mandatory parameter " << name
<< " missing (no global default and no subnet-"
<< "specific value)");
}
@@ -1086,6 +1571,9 @@ private:
/// parsers are stored here
ParserCollection parsers_;
+
+ /// Pointer to the created subnet object.
+ isc::dhcp::Subnet6Ptr subnet_;
};
/// @brief this class parses a list of subnets
@@ -1131,7 +1619,7 @@ public:
// the old one and replace with the new one.
// remove old subnets
- CfgMgr::instance().deleteSubnets6();
+ isc::dhcp::CfgMgr::instance().deleteSubnets6();
BOOST_FOREACH(ParserPtr subnet, subnets_) {
subnet->commit();
@@ -1142,7 +1630,7 @@ public:
/// @brief Returns Subnet6ListConfigParser object
/// @param param_name name of the parameter
/// @return Subnets6ListConfigParser object
- static DhcpConfigParser* Factory(const std::string& param_name) {
+ static DhcpConfigParser* factory(const std::string& param_name) {
return (new Subnets6ListConfigParser(param_name));
}
@@ -1150,6 +1638,11 @@ public:
ParserCollection subnets_;
};
+} // anonymous namespace
+
+namespace isc {
+namespace dhcp {
+
/// @brief creates global parsers
///
/// This method creates global parsers that parse global parameters, i.e.
@@ -1161,25 +1654,15 @@ public:
DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
FactoryMap factories;
- factories.insert(pair<string, ParserFactory*>(
- "preferred-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "valid-lifetime", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "renew-timer", Uint32Parser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "rebind-timer", Uint32Parser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "interface", InterfaceListConfigParser::Factory));
- factories.insert(pair<string, ParserFactory*>(
- "subnet6", Subnets6ListConfigParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "option-data", OptionDataListParser::Factory));
-
- factories.insert(pair<string, ParserFactory*>(
- "version", StringParser::Factory));
+ factories["preferred-lifetime"] = Uint32Parser::factory;
+ factories["valid-lifetime"] = Uint32Parser::factory;
+ factories["renew-timer"] = Uint32Parser::factory;
+ factories["rebind-timer"] = Uint32Parser::factory;
+ factories["interface"] = InterfaceListConfigParser::factory;
+ factories["subnet6"] = Subnets6ListConfigParser::factory;
+ factories["option-data"] = OptionDataListParser::factory;
+ factories["option-def"] = OptionDefListParser::factory;
+ factories["version"] = StringParser::factory;
FactoryMap::iterator f = factories.find(config_id);
if (f == factories.end()) {
@@ -1193,23 +1676,8 @@ DhcpConfigParser* createGlobalDhcpConfigParser(const std::string& config_id) {
return (f->second(config_id));
}
-/// @brief configures DHCPv6 server
-///
-/// This function is called every time a new configuration is received. The extra
-/// parameter is a reference to DHCPv6 server component. It is currently not used
-/// and CfgMgr::instance() is accessed instead.
-///
-/// This method does not throw. It catches all exceptions and returns them as
-/// reconfiguration statuses. It may return the following response codes:
-/// 0 - configuration successful
-/// 1 - malformed configuration (parsing failed)
-/// 2 - logical error (parsing was successful, but the values are invalid)
-///
-/// @param config_set a new configuration for DHCPv6 server
-/// @return answer that contains result of reconfiguration
-/// @throw Dhcp6ConfigError if trying to create a parser for NULL config
ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
+configureDhcp6Server(Dhcpv6Srv&, ConstElementPtr config_set) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
@@ -1221,42 +1689,126 @@ configureDhcp6Server(Dhcpv6Srv& , ConstElementPtr config_set) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_START).arg(config_set->str());
- ParserCollection parsers;
+ // Some of the values specified in the configuration depend on
+ // other values. Typically, the values in the subnet4 structure
+ // depend on the global values. Also, option values configuration
+ // must be performed after the option definitions configurations.
+ // Thus we group parsers and will fire them in the right order:
+ // all parsers other than subnet4 and option-data parser,
+ // option-data parser, subnet4 parser.
+ ParserCollection independent_parsers;
+ ParserPtr subnet_parser;
+ ParserPtr option_parser;
+
+ // The subnet parsers implement data inheritance by directly
+ // accessing global storage. For this reason the global data
+ // parsers must store the parsed data into global storages
+ // immediately. This may cause data inconsistency if the
+ // parsing operation fails after the global storage has been
+ // modified. We need to preserve the original global data here
+ // so as we can rollback changes when an error occurs.
+ Uint32Storage uint32_local(uint32_defaults);
+ StringStorage string_local(string_defaults);
+ OptionStorage option_local(option_defaults);
+ OptionDefStorage option_def_local(option_def_intermediate);
+
+ // answer will hold the result.
+ ConstElementPtr answer;
+ // rollback informs whether error occured and original data
+ // have to be restored to global storages.
+ bool rollback = false;
try {
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ // Make parsers grouping.
+ const std::map<std::string, ConstElementPtr>& values_map =
+ config_set->mapValue();
+ BOOST_FOREACH(ConfigPair config_pair, values_map) {
ParserPtr parser(createGlobalDhcpConfigParser(config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
+ if (config_pair.first == "subnet6") {
+ subnet_parser = parser;
+
+ } else if (config_pair.first == "option-data") {
+ option_parser = parser;
+
+ } else {
+ // Those parsers should be started before other
+ // parsers so we can call build straight away.
+ independent_parsers.push_back(parser);
+ parser->build(config_pair.second);
+ // The commit operation here may modify the global storage
+ // but we need it so as the subnet6 parser can access the
+ // parsed data.
+ parser->commit();
+ }
+ }
+
+ // The option values parser is the next one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator option_config =
+ values_map.find("option-data");
+ if (option_config != values_map.end()) {
+ option_parser->build(option_config->second);
+ option_parser->commit();
+ }
+
+ // The subnet parser is the last one to be run.
+ std::map<std::string, ConstElementPtr>::const_iterator subnet_config =
+ values_map.find("subnet6");
+ if (subnet_config != values_map.end()) {
+ subnet_parser->build(subnet_config->second);
}
+
} catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed:") + ex.what());
- return (answer);
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed: ") + ex.what());
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
+
} catch (...) {
// for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(1,
- string("Configuration parsing failed"));
+ answer =
+ isc::config::createAnswer(1, string("Configuration parsing failed"));
+ // An error occured, so make sure that we restore original data.
+ rollback = true;
}
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
+ // So far so good, there was no parsing error so let's commit the
+ // configuration. This will add created subnets and option values into
+ // the server's configuration.
+ // This operation should be exception safe but let's make sure.
+ if (!rollback) {
+ try {
+ if (subnet_parser) {
+ subnet_parser->commit();
+ }
+ }
+ catch (const isc::Exception& ex) {
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed:")
+ + ex.what());
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
+ } catch (...) {
+ // for things like bad_cast in boost::lexical_cast
+ answer =
+ isc::config::createAnswer(2, string("Configuration commit failed"));
+ // An error occured, so make sure to restore the original data.
+ rollback = true;
}
}
- catch (const isc::Exception& ex) {
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed:") + ex.what());
+
+ // Rollback changes as the configuration parsing failed.
+ if (rollback) {
+ std::swap(uint32_defaults, uint32_local);
+ std::swap(string_defaults, string_local);
+ std::swap(option_defaults, option_local);
+ std::swap(option_def_intermediate, option_def_local);
return (answer);
- } catch (...) {
- // for things like bad_cast in boost::lexical_cast
- ConstElementPtr answer = isc::config::createAnswer(2,
- string("Configuration commit failed"));
}
LOG_INFO(dhcp6_logger, DHCP6_CONFIG_COMPLETE).arg(config_details);
- ConstElementPtr answer = isc::config::createAnswer(0, "Configuration commited.");
+ // Everything was fine. Configuration is successful.
+ answer = isc::config::createAnswer(0, "Configuration commited.");
return (answer);
}
diff --git a/src/bin/dhcp6/config_parser.h b/src/bin/dhcp6/config_parser.h
index ed44bb9..6d7a807 100644
--- a/src/bin/dhcp6/config_parser.h
+++ b/src/bin/dhcp6/config_parser.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -27,129 +27,25 @@ namespace dhcp {
class Dhcpv6Srv;
-/// An exception that is thrown if an error occurs while configuring an
-/// \c Dhcpv6Srv object.
-class Dhcp6ConfigError : public isc::Exception {
-public:
-
- /// @brief constructor
- ///
- /// @param file name of the file, where exception occurred
- /// @param line line of the file, where exception occurred
- /// @param what text description of the issue that caused exception
- Dhcp6ConfigError(const char* file, size_t line, const char* what)
- : isc::Exception(file, line, what) {}
-};
-
-/// @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
- ///
- /// Note: The copy constructor and the assignment operator are
- /// intentionally defined as private to make it explicit that this is a
- /// pure base class.
- //@{
-private:
- DhcpConfigParser(const DhcpConfigParser& source);
- DhcpConfigParser& operator=(const DhcpConfigParser& source);
-protected:
- /// \brief The default constructor.
- ///
- /// This is intentionally defined as \c protected as this base class should
- /// never be instantiated (except as part of a derived class).
- DhcpConfigParser() {}
-public:
- /// The destructor.
- virtual ~DhcpConfigParser() {}
- //@}
-
- /// \brief Prepare configuration value.
- ///
- /// This method parses the "value part" of the configuration identifier
- /// that corresponds to this derived class and prepares a new value to
- /// apply to the server.
- ///
- /// This method must validate the given value both in terms of syntax
- /// and semantics of the configuration, so that the server will be
- /// validly configured at the time of \c commit(). Note: the given
- /// configuration value is normally syntactically validated, but the
- /// \c build() implementation must also expect invalid input. If it
- /// detects an error it may throw an exception of a derived class
- /// of \c isc::Exception.
- ///
- /// Preparing a configuration value will often require resource
- /// allocation. If it fails, it may throw a corresponding standard
- /// exception.
- ///
- /// This method is not expected to be called more than once in the
- /// life of the object. Although multiple calls are not prohibited
- /// by the interface, the behavior is undefined.
- ///
- /// \param config_value The configuration value for the identifier
- /// corresponding to the derived class.
- virtual void build(isc::data::ConstElementPtr config_value) = 0;
-
- /// \brief Apply the prepared configuration value to the server.
- ///
- /// This method is expected to be exception free, and, as a consequence,
- /// it should normally not involve resource allocation.
- /// Typically it would simply perform exception free assignment or swap
- /// operation on the value prepared in \c build().
- /// In some cases, however, it may be very difficult to meet this
- /// condition in a realistic way, while the failure case should really
- /// be very rare. In such a case it may throw, and, if the parser is
- /// called via \c configureDhcp6Server(), the caller will convert the
- /// exception as a fatal error.
- ///
- /// This method is expected to be called after \c build(), and only once.
- /// The result is undefined otherwise.
- virtual void commit() = 0;
-};
-
-/// @brief a pointer to configuration parser
-typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
-
-/// @brief a collection of parsers
-///
-/// This container is used to store pointer to parsers for a given scope.
-typedef std::vector<ParserPtr> ParserCollection;
-
-
-/// \brief Configure an \c Dhcpv6Srv object with a set of configuration values.
+/// @brief Configures DHCPv6 server
///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c DhcpConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
+/// This function is called every time a new configuration is received. The extra
+/// parameter is a reference to DHCPv6 server component. It is currently not used
+/// and CfgMgr::instance() is accessed instead.
///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c Dhcp6ConfigError.
-/// If the given configuration requires resource allocation and it fails,
-/// a corresponding standard exception will be thrown.
-/// Other exceptions may also be thrown, depending on the implementation of
-/// the underlying derived class of \c Dhcp6ConfigError.
-/// In any case the strong guarantee is provided as described above except
-/// in the very rare cases where the \c commit() method of a parser throws
-/// an exception. If that happens this function converts the exception
-/// into a \c FatalError exception and rethrows it. This exception is
-/// expected to be caught at the highest level of the application to terminate
-/// the program gracefully.
+/// This method does not throw. It catches all exceptions and returns them as
+/// reconfiguration statuses. It may return the following response codes:
+/// 0 - configuration successful
+/// 1 - malformed configuration (parsing failed)
+/// 2 - commit failed (parsing was successful, but the values could not be
+/// stored in the configuration).
///
-/// \param server The \c Dhcpv6Srv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
+/// @param server DHCPv6 server object.
+/// @param config_set a new configuration for DHCPv6 server.
+/// @return answer that contains result of the reconfiguration.
+/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
-configureDhcp6Server(Dhcpv6Srv& server,
- isc::data::ConstElementPtr config_set);
+configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
}; // end of isc::dhcp namespace
}; // end of isc namespace
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 8611365..5505a36 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -19,6 +19,7 @@
#include <cc/session.h>
#include <config/ccsession.h>
#include <dhcp/iface_mgr.h>
+#include <dhcpsrv/dhcp_config_parser.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
@@ -41,7 +42,6 @@ using namespace std;
namespace isc {
namespace dhcp {
-
ControlledDhcpv6Srv* ControlledDhcpv6Srv::server_ = NULL;
ConstElementPtr
@@ -121,7 +121,7 @@ void ControlledDhcpv6Srv::establishSession() {
try {
configureDhcp6Server(*this, config_session_->getFullConfig());
- } catch (const Dhcp6ConfigError& ex) {
+ } catch (const DhcpConfigError& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_LOAD_FAIL).arg(ex.what());
}
diff --git a/src/bin/dhcp6/dhcp6.dox b/src/bin/dhcp6/dhcp6.dox
index 5350fd8..fa37769 100644
--- a/src/bin/dhcp6/dhcp6.dox
+++ b/src/bin/dhcp6/dhcp6.dox
@@ -35,19 +35,19 @@
This method iterates over list of received configuration elements and creates a
list of parsers for each received entry. Parser is an object that is derived
- from a \ref isc::dhcp::DhcpConfigParser class. Once a parser is created
+ from a DhcpConfigParser class. Once a parser is created
(constructor), its value is set (using build() method). Once all parsers are
build, the configuration is then applied ("commited") and commit() method is
called.
All parsers are defined in src/bin/dhcp6/config_parser.cc file. Some of them
- are generic (e.g. \ref isc::dhcp::Uint32Parser that is able to handle any
- unsigned 32 bit integer), but some are very specialized (e.g. \ref
- isc::dhcp::Subnets6ListConfigParser parses definitions of Subnet6 lists). In
- some cases, e.g. subnet6 definitions, the configuration entry is not a simple
- value, but a map or a list itself. In such case, the parser iterates over all
- elements and creates parsers for a given scope. This process may be repeated
- (sort of) recursively.
+ are generic (e.g. Uint32Parser that is able to handle any
+ unsigned 32 bit integer), but some are very specialized (e.g.
+ Subnets6ListConfigParser parses definitions of Subnet6 lists). In some cases,
+ e.g. subnet6 definitions, the configuration entry is not a simple value, but
+ a map or a list itself. In such case, the parser iterates over all elements
+ and creates parsers for a given scope. This process may be repeated (sort of)
+ recursively.
@section dhcpv6ConfigInherit DHCPv6 Configuration Inheritance
@@ -55,16 +55,16 @@
For example, renew-timer value may be specified at a global scope and it then
applies to all subnets. However, some subnets may have it overwritten with more
specific values that takes precedence over global values that are considered
- defaults. Some parsers (e.g. \ref isc::dhcp::Uint32Parser and \ref
- isc::dhcp::StringParser) implement that inheritance. By default, they store
- values in global uint32_defaults and string_defaults storages. However, it is
- possible to instruct them to store parsed values in more specific
- storages. That capability is used, e.g. in \ref isc::dhcp::Subnet6ConfigParser
- that has its own storage that is unique for each subnet. Finally, during commit
- phase (commit() method), appropriate parsers can use apply parameter inheritance.
+ defaults. Some parsers (e.g. Uint32Parser and StringParser) implement that
+ inheritance. By default, they store values in global uint32_defaults and
+ string_defaults storages. However, it is possible to instruct them to store
+ parsed values in more specific storages. That capability is used, e.g. in
+ Subnet6ConfigParser that has its own storage that is unique for each subnet.
+ Finally, during commit phase (commit() method), appropriate parsers can use
+ apply parameter inheritance.
Debugging configuration parser may be confusing. Therefore there is a special
- class called \ref isc::dhcp::DebugParser. It does not configure anything, but just
+ class called DebugParser. It does not configure anything, but just
accepts any parameter of any type. If requested to commit configuration, it will
print out received parameter name and its value. This class is not currently used,
but it is convenient to have it every time a new parameter is added to DHCP
diff --git a/src/bin/dhcp6/dhcp6.spec b/src/bin/dhcp6/dhcp6.spec
index c5e9565..7f80457 100644
--- a/src/bin/dhcp6/dhcp6.spec
+++ b/src/bin/dhcp6/dhcp6.spec
@@ -40,6 +40,56 @@
"item_default": 4000
},
+ { "item_name": "option-def",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec":
+ {
+ "item_name": "single-option-def",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
+ { "item_name": "code",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ },
+
+ { "item_name": "type",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "array",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+
+ { "item_name": "record_types",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "",
+ },
+
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ } ]
+ }
+ },
+
{ "item_name": "option-data",
"item_type": "list",
"item_optional": false,
@@ -67,6 +117,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
},
@@ -89,6 +149,12 @@
"item_default": ""
},
+ { "item_name": "interface",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ },
+
{ "item_name": "renew-timer",
"item_type": "integer",
"item_optional": false,
@@ -152,6 +218,16 @@
"item_type": "string",
"item_optional": false,
"item_default": ""
+ },
+ { "item_name": "csv-format",
+ "item_type": "boolean",
+ "item_optional": false,
+ "item_default": False
+ },
+ { "item_name": "space",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "dhcp6"
} ]
}
} ]
diff --git a/src/bin/dhcp6/dhcp6_messages.mes b/src/bin/dhcp6/dhcp6_messages.mes
index 6ef8455..c38776c 100644
--- a/src/bin/dhcp6/dhcp6_messages.mes
+++ b/src/bin/dhcp6/dhcp6_messages.mes
@@ -61,15 +61,22 @@ A debug message indicating that the IPv6 DHCP server has received an
updated configuration from the BIND 10 configuration system.
% DHCP6_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
-This informational message is printed every time DHCPv6 is started.
-It indicates what database backend type is being to store lease and
-other information.
+This informational message is printed every time the IPv6 DHCP server
+is started. It indicates what database backend type is being to store
+lease and other information.
+
+% DHCP6_LEASE_WITHOUT_DUID lease for address %1 does not have a DUID
+This error message indicates a database consistency failure. The lease
+database has an entry indicating that the given address is in use,
+but the lease does not contain any client identification. This is most
+likely due to a software error: please raise a bug report. As a temporary
+workaround, manually remove the lease entry from the database.
% DHCP6_LEASE_ADVERT lease %1 advertised (client duid=%2, iaid=%3)
This debug message indicates that the server successfully advertised
-a lease. It is up to the client to choose one server out of othe advertised
-and continue allocation with that server. This is a normal behavior and
-indicates successful operation.
+a lease. It is up to the client to choose one server out of the
+advertised servers and continue allocation with that server. This
+is a normal behavior and indicates successful operation.
% DHCP6_LEASE_ADVERT_FAIL failed to advertise a lease for client duid=%1, iaid=%2
This message indicates that the server failed to advertise (in response to
@@ -79,13 +86,44 @@ such failure. Each specific failure is logged in a separate log entry.
% DHCP6_LEASE_ALLOC lease %1 has been allocated (client duid=%2, iaid=%3)
This debug message indicates that the server successfully granted (in
response to client's REQUEST message) a lease. This is a normal behavior
-and incicates successful operation.
+and indicates successful operation.
% DHCP6_LEASE_ALLOC_FAIL failed to grant a lease for client duid=%1, iaid=%2
This message indicates that the server failed to grant (in response to
received REQUEST) a lease for a given client. There may be many reasons for
such failure. Each specific failure is logged in a separate log entry.
+% DHCP6_RELEASE address %1 belonging to client duid=%2, iaid=%3 was released properly.
+This debug message indicates that an address was released properly. It
+is a normal operation during client shutdown.
+
+% DHCP6_RELEASE_FAIL failed to remove lease for address %1 for duid=%2, iaid=%3
+This error message indicates that the software failed to remove a
+lease from the lease database. It probably due to an error during a
+database operation: resolution will most likely require administrator
+intervention (e.g. check if DHCP process has sufficient privileges to
+update the database). It may also be triggered if a lease was manually
+removed from the database during RELEASE message processing.
+
+% DHCP6_RELEASE_FAIL_WRONG_DUID client (duid=%1) tried to release address %2, but it belongs to client (duid=%3)
+This warning message indicates that client tried to release an address
+that belongs to a different client. This should not happen in normal
+circumstances and may indicate a misconfiguration of the client. However,
+since the client releasing the address will stop using it anyway, there
+is a good chance that the situation will correct itself.
+
+% DHCP6_RELEASE_FAIL_WRONG_IAID client (duid=%1) tried to release address %2, but it used wrong IAID (expected %3, but got %4)
+This warning message indicates that client tried to release an address
+that does belong to it, but the address was expected to be in a different
+IA (identity association) container. This probably means that the client's
+support for multiple addresses is flawed.
+
+% DHCP6_RELEASE_MISSING_CLIENTID client (address=%1) sent RELEASE message without mandatory client-id
+This warning message indicates that client sent RELEASE message without
+mandatory client-id option. This is most likely caused by a buggy client
+(or a relay that malformed forwarded message). This request will not be
+processed and a response with error status code will be sent back.
+
% DHCP6_NOT_RUNNING IPv6 DHCP server is not running
A warning message is issued when an attempt is made to shut down the
IPv6 DHCP server but it is not running.
@@ -121,7 +159,7 @@ a received OFFER packet as UNKNOWN).
% DHCP6_PACKET_RECEIVE_FAIL error on attempt to receive packet: %1
The IPv6 DHCP server tried to receive a packet but an error
-occured during this attempt. The reason for the error is included in
+occurred during this attempt. The reason for the error is included in
the message.
% DHCP6_PACKET_SEND_FAIL failed to send DHCPv6 packet: %1
@@ -156,6 +194,34 @@ A debug message listing the data returned to the client.
The IPv6 DHCP server has encountered a fatal error and is terminating.
The reason for the failure is included in the message.
+% DHCP6_SERVERID_GENERATED server-id %1 has been generated and will be stored in %2
+This informational messages indicates that the server was not able to read
+its server identifier (DUID) and has generated a new one. This server-id
+will be stored in a file and will be read and used during next restart. It
+is normal behavior when the server is started for the first time. If
+this message is printed every start, please check that the server have
+sufficient permission to write its server-id file and that the file is not
+corrupt.
+
+Changing the server identifier in a production environment is not
+recommended as existing clients will not recognize the server and may go
+through a rebind phase. However, they should be able to recover without
+losing their leases.
+
+% DHCP6_SERVERID_LOADED server-id %1 has been loaded from file %2
+This debug message indicates that the server loaded its server identifier.
+That value is sent in all server responses and clients use it to
+discriminate between servers. This is a part of normal startup or
+reconfiguration procedure.
+
+% DHCP6_SERVERID_WRITE_FAIL server was not able to write its ID to file %1
+This warning message indicates that server was not able to write its
+server identifier (DUID) to a file. This likely indicates lack of write
+permission to a given file or directory. This is not cricital and the
+server will continue to operate, but server will generate different DUID
+during every start and clients will need to go through a rebind phase
+to recover.
+
% DHCP6_SESSION_FAIL failed to establish BIND 10 session (%1), running stand-alone
The server has failed to establish communication with the rest of BIND
10 and is running in stand-alone mode. (This behavior will change once
@@ -216,3 +282,8 @@ recently and does not recognize its well-behaving clients. This is more
probable if you see many such messages. Clients will recover from this,
but they will most likely get a different IP addresses and experience
a brief service interruption.
+
+% DHCP6_UNKNOWN_RELEASE received RELEASE from unknown client (duid=%1, iaid=%2)
+This warning message is printed when client attempts to release a lease,
+but no such lease is known by the server. See DHCP6_UNKNOWN_RENEW for
+possible reasons for such behavior.
diff --git a/src/bin/dhcp6/dhcp6_srv.cc b/src/bin/dhcp6/dhcp6_srv.cc
index a50be03..e50950f 100644
--- a/src/bin/dhcp6/dhcp6_srv.cc
+++ b/src/bin/dhcp6/dhcp6_srv.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2011-2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2011-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -32,14 +32,20 @@
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/utils.h>
#include <exceptions/exceptions.h>
#include <util/io_utilities.h>
#include <util/range_utilities.h>
+#include <util/encode/hex.h>
#include <boost/foreach.hpp>
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/erase.hpp>
#include <stdlib.h>
#include <time.h>
+#include <iomanip>
+#include <fstream>
using namespace isc;
using namespace isc::asiolink;
@@ -69,7 +75,22 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t port, const char* dbconfig)
IfaceMgr::instance().openSockets6(port);
}
- setServerID();
+ string duid_file = CfgMgr::instance().getDataDir() + "/" + string(SERVER_DUID_FILE);
+ if (loadServerID(duid_file)) {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_SERVERID_LOADED)
+ .arg(duid_file);
+ } else {
+ generateServerID();
+ LOG_INFO(dhcp6_logger, DHCP6_SERVERID_GENERATED)
+ .arg(duidToString(getServerID()))
+ .arg(duid_file);
+
+ if (!writeServerID(duid_file)) {
+ LOG_WARN(dhcp6_logger, DHCP6_SERVERID_WRITE_FAIL)
+ .arg(duid_file);
+ }
+
+ }
// Instantiate LeaseMgr
LeaseMgrFactory::create(dbconfig);
@@ -208,10 +229,67 @@ bool Dhcpv6Srv::run() {
return (true);
}
-void Dhcpv6Srv::setServerID() {
+bool Dhcpv6Srv::loadServerID(const std::string& file_name) {
+
+ // load content of the file into a string
+ fstream f(file_name.c_str(), ios::in);
+ if (!f.is_open()) {
+ return (false);
+ }
+
+ string hex_string;
+ f >> hex_string;
+ f.close();
+
+ // remove any spaces
+ boost::algorithm::erase_all(hex_string, " ");
+
+ // now remove :
+ /// @todo: We should check first if the format is sane.
+ /// Otherwise 1:2:3:4 will be converted to 0x12, 0x34
+ boost::algorithm::erase_all(hex_string, ":");
+
+ std::vector<uint8_t> bin;
+
+ // Decode the hex string and store it in bin (which happens
+ // to be OptionBuffer format)
+ isc::util::encode::decodeHex(hex_string, bin);
+
+ // Now create server-id option
+ serverid_.reset(new Option(Option::V6, D6O_SERVERID, bin));
+
+ return (true);
+}
+
+std::string Dhcpv6Srv::duidToString(const OptionPtr& opt) {
+ stringstream tmp;
- /// @todo: DUID should be generated once and then stored, rather
- /// than generated each time
+ OptionBuffer data = opt->getData();
+
+ bool colon = false;
+ for (OptionBufferConstIter it = data.begin(); it != data.end(); ++it) {
+ if (colon) {
+ tmp << ":";
+ }
+ tmp << hex << setw(2) << setfill('0') << static_cast<uint16_t>(*it);
+ if (!colon) {
+ colon = true;
+ }
+ }
+
+ return tmp.str();
+}
+
+bool Dhcpv6Srv::writeServerID(const std::string& file_name) {
+ fstream f(file_name.c_str(), ios::out | ios::trunc);
+ if (!f.good()) {
+ return (false);
+ }
+ f << duidToString(getServerID());
+ f.close();
+}
+
+void Dhcpv6Srv::generateServerID() {
/// @todo: This code implements support for DUID-LLT (the recommended one).
/// We should eventually add support for other DUID types: DUID-LL, DUID-EN
@@ -340,8 +418,8 @@ void Dhcpv6Srv::appendRequestedOptions(const Pkt6Ptr& question, Pkt6Ptr& answer)
// Get the list of options that client requested.
const std::vector<uint16_t>& requested_opts = option_oro->getValues();
// Get the list of options configured for a subnet.
- const Subnet::OptionContainer& options = subnet->getOptions();
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Try to match requested options with those configured for a subnet.
// If match is found, append configured option to the answer message.
BOOST_FOREACH(uint16_t opt, requested_opts) {
@@ -404,8 +482,8 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
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 "
+ if (!server_ids.empty()) {
+ isc_throw(RFCViolation, "Server-id option was not expected, but "
<< server_ids.size() << " received in " << pkt->getName());
}
break;
@@ -427,7 +505,17 @@ void Dhcpv6Srv::sanityCheck(const Pkt6Ptr& pkt, RequirementLevel clientid,
}
Subnet6Ptr Dhcpv6Srv::selectSubnet(const Pkt6Ptr& question) {
- Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
+
+ /// @todo: pass interface information only if received direct (non-relayed) message
+
+ // Try to find a subnet if received packet from a directly connected client
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(question->getIface());
+ if (subnet) {
+ return (subnet);
+ }
+
+ // If no subnet was found, try to find it based on remote address
+ subnet = CfgMgr::instance().getSubnet6(question->getRemoteAddr());
return (subnet);
}
@@ -436,6 +524,8 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// We need to allocate addresses for all IA_NA options in the client's
// question (i.e. SOLICIT or REQUEST) message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(question);
@@ -450,7 +540,10 @@ void Dhcpv6Srv::assignLeases(const Pkt6Ptr& question, Pkt6Ptr& answer) {
// perhaps this should be logged on some higher level? This is most likely
// configuration bug.
- LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED);
+ LOG_ERROR(dhcp6_logger, DHCP6_SUBNET_SELECTION_FAILED)
+ .arg(question->getRemoteAddr().toText())
+ .arg(question->getName());
+
} else {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL_DATA, DHCP6_SUBNET_SELECTED)
.arg(subnet->toText());
@@ -604,7 +697,7 @@ OptionPtr Dhcpv6Srv::renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
// Insert status code NoAddrsAvail.
- ia_rsp->addOption(createStatusCode(STATUS_NoAddrsAvail,
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
"Sorry, no known leases for this duid/iaid."));
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_UNKNOWN_RENEW)
@@ -640,6 +733,8 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
// We need to renew addresses for all IA_NA options in the client's
// RENEW message.
+ // @todo add support for IA_TA
+ // @todo add support for IA_PD
// We need to select a subnet the client is connected in.
Subnet6Ptr subnet = selectSubnet(renew);
@@ -688,11 +783,176 @@ void Dhcpv6Srv::renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply) {
break;
}
}
+}
+
+void Dhcpv6Srv::releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply) {
+
+ // We need to release addresses for all IA_NA options in the client's
+ // RELEASE message.
+ // @todo Add support for IA_TA
+ // @todo Add support for IA_PD
+ // @todo Consider supporting more than one address in a single IA_NA.
+ // That was envisaged by RFC3315, but it never happened. The only
+ // software that supports that is Dibbler, but its author seriously doubts
+ // if anyone is really using it. Clients that want more than one address
+ // just include more instances of IA_NA options.
+
+ // Let's find client's DUID. Client is supposed to include its client-id
+ // option almost all the time (the only exception is an anonymous inf-request,
+ // but that is mostly a theoretical case). Our allocation engine needs DUID
+ // and will refuse to allocate anything to anonymous clients.
+ OptionPtr opt_duid = release->getOption(D6O_CLIENTID);
+ if (!opt_duid) {
+ // This should not happen. We have checked this before.
+ // see sanityCheck() called from processRelease()
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_MISSING_CLIENTID)
+ .arg(release->getRemoteAddr().toText());
+
+ reply->addOption(createStatusCode(STATUS_UnspecFail,
+ "You did not include mandatory client-id"));
+ return;
+ }
+ DuidPtr duid(new DUID(opt_duid->getData()));
+
+ int general_status = STATUS_Success;
+ for (Option::OptionCollection::iterator opt = release->options_.begin();
+ opt != release->options_.end(); ++opt) {
+ switch (opt->second->getType()) {
+ case D6O_IA_NA: {
+ OptionPtr answer_opt = releaseIA_NA(duid, release, general_status,
+ boost::dynamic_pointer_cast<Option6IA>(opt->second));
+ if (answer_opt) {
+ reply->addOption(answer_opt);
+ }
+ break;
+ }
+ // @todo: add support for IA_PD
+ // @todo: add support for IA_TA
+ default:
+ // remaining options are stateless and thus ignored in this context
+ ;
+ }
+ }
+
+ // To be pedantic, we should also include status code in the top-level
+ // scope, not just in each IA_NA. See RFC3315, section 18.2.6.
+ // This behavior will likely go away in RFC3315bis.
+ reply->addOption(createStatusCode(general_status,
+ "Summary status for all processed IA_NAs"));
+}
+
+OptionPtr Dhcpv6Srv::releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia) {
+ // Release can be done in one of two ways:
+ // Approach 1: extract address from client's IA_NA and see if it belongs
+ // to this particular client.
+ // Approach 2: find a subnet for this client, get a lease for
+ // this subnet/duid/iaid and check if its content matches to what the
+ // client is asking us to release.
+ //
+ // This method implements approach 1.
+
+ // That's our response
+ boost::shared_ptr<Option6IA> ia_rsp(new Option6IA(D6O_IA_NA, ia->getIAID()));
+
+ boost::shared_ptr<Option6IAAddr> release_addr = boost::dynamic_pointer_cast<Option6IAAddr>
+ (ia->getOption(D6O_IAADDR));
+ if (!release_addr) {
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "You did not include address in your RELEASE"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ Lease6Ptr lease = LeaseMgrFactory::instance().getLease6(release_addr->getAddress());
+
+ if (!lease) {
+ // client releasing a lease that we don't know about.
+
+ // Insert status code NoAddrsAvail.
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "Sorry, no known leases for this duid/iaid, can't release."));
+ general_status = STATUS_NoBinding;
+
+ LOG_INFO(dhcp6_logger, DHCP6_UNKNOWN_RELEASE)
+ .arg(duid->toText())
+ .arg(ia->getIAID());
+
+ return (ia_rsp);
+ }
+
+ if (!lease->duid_) {
+ // Something is gravely wrong here. We do have a lease, but it does not
+ // have mandatory DUID information attached. Someone was messing with our
+ // database.
+
+ LOG_ERROR(dhcp6_logger, DHCP6_LEASE_WITHOUT_DUID)
+ .arg(release_addr->getAddress().toText());
+
+ general_status = STATUS_UnspecFail;
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Database consistency check failed when trying to RELEASE"));
+ return (ia_rsp);
+ }
+
+ if (*duid != *(lease->duid_)) {
+ // Sorry, it's not your address. You can't release it.
+
+ LOG_INFO(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_DUID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->duid_->toText());
+
+ general_status = STATUS_NoBinding;
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This address does not belong to you, you can't release it"));
+ return (ia_rsp);
+ }
+ if (ia->getIAID() != lease->iaid_) {
+ // This address belongs to this client, but to a different IA
+ LOG_WARN(dhcp6_logger, DHCP6_RELEASE_FAIL_WRONG_IAID)
+ .arg(duid->toText())
+ .arg(release_addr->getAddress().toText())
+ .arg(lease->iaid_)
+ .arg(ia->getIAID());
+ ia_rsp->addOption(createStatusCode(STATUS_NoBinding,
+ "This is your address, but you used wrong IAID"));
+ general_status = STATUS_NoBinding;
+ return (ia_rsp);
+ }
+
+ // It is not necessary to check if the address matches as we used
+ // getLease6(addr) method that is supposed to return a proper lease.
+ // Ok, we've passed all checks. Let's release this address.
+ if (!LeaseMgrFactory::instance().deleteLease(lease->addr_)) {
+ ia_rsp->addOption(createStatusCode(STATUS_UnspecFail,
+ "Server failed to release a lease"));
+
+ LOG_ERROR(dhcp6_logger, DHCP6_RELEASE_FAIL)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+ general_status = STATUS_UnspecFail;
+
+ return (ia_rsp);
+ } else {
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_DETAIL, DHCP6_RELEASE)
+ .arg(lease->addr_.toText())
+ .arg(duid->toText())
+ .arg(lease->iaid_);
+
+ ia_rsp->addOption(createStatusCode(STATUS_Success,
+ "Lease released. Thank you, please come again."));
+
+ return (ia_rsp);
+ }
}
+
Pkt6Ptr Dhcpv6Srv::processSolicit(const Pkt6Ptr& solicit) {
sanityCheck(solicit, MANDATORY, FORBIDDEN);
@@ -751,8 +1011,16 @@ Pkt6Ptr Dhcpv6Srv::processConfirm(const Pkt6Ptr& confirm) {
}
Pkt6Ptr Dhcpv6Srv::processRelease(const Pkt6Ptr& release) {
- /// @todo: Implement this
+
+ sanityCheck(release, MANDATORY, MANDATORY);
+
Pkt6Ptr reply(new Pkt6(DHCPV6_REPLY, release->getTransid()));
+
+ copyDefaultOptions(release, reply);
+ appendDefaultOptions(release, reply);
+
+ releaseLeases(release, reply);
+
return reply;
}
diff --git a/src/bin/dhcp6/dhcp6_srv.h b/src/bin/dhcp6/dhcp6_srv.h
index b6b0306..6515543 100644
--- a/src/bin/dhcp6/dhcp6_srv.h
+++ b/src/bin/dhcp6/dhcp6_srv.h
@@ -31,21 +31,15 @@
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
+/// @brief file name of a server-id file
///
-/// @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) {}
-};
-
-
+/// Server must store its duid in persistent storage that must not change
+/// between restarts. This is name of the file that is created in dataDir
+/// (see isc::dhcp::CfgMgr::getDataDir()). It is a text file that uses
+/// double digit hex values separated by colons format, e.g.
+/// 01:ff:02:03:06:80:90:ab:cd:ef. Server will create it during first
+/// run and then use it afterwards.
+static const char* SERVER_DUID_FILE = "b10-dhcp6-serverid";
/// @brief DHCPv6 server service.
///
@@ -83,7 +77,7 @@ public:
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
Dhcpv6Srv(uint16_t port = DHCP6_SERVER_PORT,
- const char* dbconfig = "type=memfile");
+ const char* dbconfig = "type=memfile");
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
@@ -212,17 +206,39 @@ protected:
/// @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.
+ /// Generates response to IA_NA in Renew. This typically includes finding a
+ /// lease that corresponds to the received address. If no such lease is
+ /// found, an IA_NA response is generated with an appropriate status code.
///
/// @param subnet subnet the sender belongs to
/// @param duid client's duid
/// @param question client's message
/// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
OptionPtr renewIA_NA(const Subnet6Ptr& subnet, const DuidPtr& duid,
Pkt6Ptr question, boost::shared_ptr<Option6IA> ia);
+ /// @brief Releases specific IA_NA option
+ ///
+ /// Generates response to IA_NA in Release message. This covers finding and
+ /// removal of a lease that corresponds to the received address. If no such
+ /// lease is found, an IA_NA response is generated with an appropriate
+ /// status code.
+ ///
+ /// As RFC 3315 requires that a single status code be sent for the whole message,
+ /// this method may update the passed general_status: it is set to SUCCESS when
+ /// message processing begins, but may be updated to some error code if the
+ /// release process fails.
+ ///
+ /// @param duid client's duid
+ /// @param question client's message
+ /// @param general_status a global status (it may be updated in case of errors)
+ /// @param ia IA_NA option that is being renewed
+ /// @return IA_NA option (server's response)
+ OptionPtr releaseIA_NA(const DuidPtr& duid, Pkt6Ptr question,
+ int& general_status,
+ boost::shared_ptr<Option6IA> ia);
+
/// @brief Copies required options from client message to server answer.
///
/// Copies options that must appear in any server response (ADVERTISE, REPLY)
@@ -271,17 +287,52 @@ protected:
/// @param reply server's response
void renewLeases(const Pkt6Ptr& renew, Pkt6Ptr& reply);
+ /// @brief Attempts to release received addresses
+ ///
+ /// It iterates through received IA_NA options and attempts to release
+ /// received addresses. If no such leases are found, or the lease fails
+ /// proper checks (e.g. belongs to someone else), a proper status
+ /// code is added to reply message. Released addresses are not added
+ /// to REPLY packet, just its IA_NA containers.
+ /// @param release client's message asking to release
+ /// @param reply server's response
+ void releaseLeases(const Pkt6Ptr& release, Pkt6Ptr& reply);
+
/// @brief Sets server-identifier.
///
- /// This method attempts to set server-identifier DUID. It loads it
- /// from a file. If file load fails, it generates new DUID using
- /// interface link-layer addresses (EUI-64) + timestamp (DUID type
- /// duid-llt, see RFC3315, section 9.2). If there are no suitable
+ /// This method attempts to generate server-identifier DUID. It generates a
+ /// new DUID using interface link-layer addresses (EUI-64) + timestamp (DUID
+ /// type duid-llt, see RFC3315, section 9.2). If there are no suitable
/// interfaces present, exception it thrown
///
/// @throws isc::Unexpected Failed to read DUID file and no suitable
/// interfaces for new DUID generation are detected.
- void setServerID();
+ void generateServerID();
+
+ /// @brief attempts to load DUID from a file
+ ///
+ /// Tries to load duid from a text file. If the load is successful,
+ /// it creates server-id option and stores it in serverid_ (to be used
+ /// later by getServerID()).
+ ///
+ /// @param file_name name of the DUID file to load
+ /// @return true if load was successful, false otherwise
+ bool loadServerID(const std::string& file_name);
+
+ /// @brief attempts to write DUID to a file
+ /// Tries to write duid content (stored in serverid_) to a text file.
+ ///
+ /// @param file_name name of the DUID file to write
+ /// @return true if write was successful, false otherwise
+ bool writeServerID(const std::string& file_name);
+
+ /// @brief converts DUID to text
+ /// Converts content of DUID option to a text representation, e.g.
+ /// 01:ff:02:03:06:80:90:ab:cd:ef
+ ///
+ /// @param opt option that contains DUID
+ /// @return string representation
+ static std::string duidToString(const OptionPtr& opt);
private:
/// @brief Allocation Engine.
@@ -291,7 +342,7 @@ private:
boost::shared_ptr<AllocEngine> alloc_engine_;
/// Server DUID (to be sent in server-identifier option)
- boost::shared_ptr<isc::dhcp::Option> serverid_;
+ OptionPtr serverid_;
/// Indicates if shutdown is in progress. Setting it to true will
/// initiate server shutdown procedure.
diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc
index 7083598..b3fe88e 100644
--- a/src/bin/dhcp6/tests/config_parser_unittest.cc
+++ b/src/bin/dhcp6/tests/config_parser_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,7 @@
#include <config/ccsession.h>
#include <dhcp/libdhcp++.h>
#include <dhcp/option6_ia.h>
+#include <dhcp/iface_mgr.h>
#include <dhcp6/config_parser.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcpsrv/cfgmgr.h>
@@ -46,6 +47,23 @@ public:
// srv_(0) means to not open any sockets. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
+
+ const IfaceMgr::IfaceCollection& ifaces = IfaceMgr::instance().getIfaces();
+
+ // There must be some interface detected
+ if (ifaces.empty()) {
+ // We can't use ASSERT in constructor
+ ADD_FAILURE() << "No interfaces detected.";
+ }
+
+ valid_iface_ = ifaces.begin()->getName();
+ bogus_iface_ = "nonexisting0";
+
+ if (IfaceMgr::instance().getIface(bogus_iface_)) {
+ ADD_FAILURE() << "The '" << bogus_iface_ << "' exists on this system"
+ << " while the test assumes that it doesn't, to execute"
+ << " some negative scenarios. Can't continue this test.";
+ }
}
~Dhcp6ParserTest() {
@@ -53,10 +71,20 @@ public:
resetConfiguration();
};
+ // Checks if config_result (result of DHCP server configuration) has
+ // expected code (0 for success, other for failures).
+ // Also stores result in rcode_ and comment_.
+ void checkResult(ConstElementPtr status, int expected_code) {
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(expected_code, rcode_);
+ }
+
/// @brief Create the simple configuration with single option.
///
/// This function allows to set one of the parameters that configure
- /// option value. These parameters are: "name", "code" and "data".
+ /// option value. These parameters are: "name", "code", "data" and
+ /// "csv-format".
///
/// @param param_value string holiding option parameter value to be
/// injected into the configuration string.
@@ -67,16 +95,34 @@ public:
std::map<std::string, std::string> params;
if (parameter == "name") {
params["name"] = param_value;
- params["code"] = "80";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
+ } else if (parameter == "space") {
+ params["name"] = "subscriber-id";
+ params["space"] = param_value;
+ params["code"] = "38";
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "code") {
- params["name"] = "option_foo";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
params["code"] = param_value;
params["data"] = "AB CDEF0105";
+ params["csv-format"] = "False";
} else if (parameter == "data") {
- params["name"] = "option_foo";
- params["code"] = "80";
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
params["data"] = param_value;
+ params["csv-format"] = "False";
+ } else if (parameter == "csv-format") {
+ params["name"] = "subscriber-id";
+ params["space"] = "dhcp6";
+ params["code"] = "38";
+ params["data"] = "AB CDEF0105";
+ params["csv-format"] = param_value;
}
return (createConfigWithOption(params));
}
@@ -104,10 +150,14 @@ public:
}
if (param.first == "name") {
stream << "\"name\": \"" << param.second << "\"";
+ } else if (param.first == "space") {
+ stream << "\"space\": \"" << param.second << "\"";
} else if (param.first == "code") {
- stream << "\"code\": " << param.second << "";
+ stream << "\"code\": " << param.second;;
} else if (param.first == "data") {
stream << "\"data\": \"" << param.second << "\"";
+ } else if (param.first == "csv-format") {
+ stream << "\"csv-format\": " << param.second;
}
}
stream <<
@@ -133,6 +183,7 @@ public:
"\"renew-timer\": 1000, "
"\"valid-lifetime\": 4000, "
"\"subnet6\": [ ], "
+ "\"option-def\": [ ], "
"\"option-data\": [ ] }";
try {
@@ -228,6 +279,9 @@ public:
int rcode_;
ConstElementPtr comment_;
+
+ string valid_iface_;
+ string bogus_iface_;
};
// Goal of this test is a verification if a very simple config update
@@ -261,9 +315,8 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
EXPECT_EQ(1, rcode_);
}
-/// The goal of this test is to verify if wrongly defined subnet will
-/// be rejected. Properly defined subnet must include at least one
-/// pool definition.
+/// The goal of this test is to verify if configuration without any
+/// subnets defined can be accepted.
TEST_F(Dhcp6ParserTest, emptySubnet) {
ConstElementPtr status;
@@ -354,6 +407,99 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
EXPECT_EQ(4, subnet->getValid());
}
+// This test checks if it is possible to define a subnet with an
+// interface defined.
+TEST_F(Dhcp6ParserTest, subnetInterface) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + valid_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 0 (configuration success)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(0, rcode_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ EXPECT_EQ(valid_iface_, subnet->getIface());
+}
+
+// This test checks if invalid interface name will be rejected in
+// Subnet6 definition.
+TEST_F(Dhcp6ParserTest, subnetInterfaceBogus) {
+
+ ConstElementPtr status;
+
+ // There should be at least one interface
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"interface\": \"" + bogus_iface_ + "\","
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 1 (configuration error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ EXPECT_FALSE(subnet);
+}
+
+
+// This test checks if it is not allowed to define global interface
+// parameter.
+TEST_F(Dhcp6ParserTest, interfaceGlobal) {
+
+ ConstElementPtr status;
+
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"preferred-lifetime\": 3000,"
+ "\"rebind-timer\": 2000, "
+ "\"renew-timer\": 1000, "
+ "\"interface\": \"" + valid_iface_ + "\"," // Not valid. Can be defined in subnet only
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::1 - 2001:db8:1::ffff\" ],"
+ " \"subnet\": \"2001:db8:1::/64\" } ],"
+ "\"valid-lifetime\": 4000 }";
+ cout << config << endl;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+
+ // returned value should be 1 (parse error)
+ ASSERT_TRUE(status);
+ comment_ = parseAnswer(rcode_, status);
+ EXPECT_EQ(1, rcode_);
+}
+
// Test verifies that a subnet with pool values that do not belong to that
// pool are rejected.
TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
@@ -374,11 +520,11 @@ TEST_F(Dhcp6ParserTest, poolOutOfSubnet) {
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
- // returned value must be 2 (values error)
+ // returned value must be 1 (values error)
// as the pool does not belong to that subnet
ASSERT_TRUE(status);
comment_ = parseAnswer(rcode_, status);
- EXPECT_EQ(2, rcode_);
+ EXPECT_EQ(1, rcode_);
}
// Goal of this test is to verify if pools can be defined
@@ -415,6 +561,363 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
EXPECT_EQ(4000, subnet->getValid());
}
+// The goal of this test is to check whether an option definition
+// that defines an option carrying an IPv6 address can be created.
+TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"ipv6-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Verify that the option definition data is valid.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_FALSE(def->getArrayType());
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, def->getType());
+}
+
+// The goal of this test is to check whether an option definiiton
+// that defines an option carrying a record of data fields can
+// be created.
+TEST_F(Dhcp6ParserTest, optionDefRecord) {
+
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint16, ipv4-address, ipv6-address, string\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_RECORD_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The option comprises the record of data fields. Verify that all
+ // fields are present and they are of the expected types.
+ const OptionDefinition::RecordFieldsCollection& record_fields =
+ def->getRecordFields();
+ ASSERT_EQ(4, record_fields.size());
+ EXPECT_EQ(OPT_UINT16_TYPE, record_fields[0]);
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, record_fields[1]);
+ EXPECT_EQ(OPT_IPV6_ADDRESS_TYPE, record_fields[2]);
+ EXPECT_EQ(OPT_STRING_TYPE, record_fields[3]);
+}
+
+// The goal of this test is to verify that multiple option definitions
+// can be created.
+TEST_F(Dhcp6ParserTest, optionDefMultiple) {
+ // Configuration string.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 101,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definitions do not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 101));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Check the first definition we have created.
+ OptionDefinitionPtr def1 = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def1);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def1->getName());
+ EXPECT_EQ(100, def1->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def1->getType());
+ EXPECT_FALSE(def1->getArrayType());
+
+ // Check the second option definition we have created.
+ OptionDefinitionPtr def2 = CfgMgr::instance().getOptionDef("isc", 101);
+ ASSERT_TRUE(def2);
+
+ // Check the option data.
+ EXPECT_EQ("foo-2", def2->getName());
+ EXPECT_EQ(101, def2->getCode());
+ EXPECT_EQ(OPT_IPV4_ADDRESS_TYPE, def2->getType());
+ EXPECT_FALSE(def2->getArrayType());
+}
+
+// The goal of this test is to verify that the duplicated option
+// definition is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefDuplicate) {
+
+ // Configuration string. Both option definitions have
+ // the same code and belong to the same option space.
+ // This configuration should not be accepted.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " },"
+ " {"
+ " \"name\": \"foo-2\","
+ " \"code\": 100,"
+ " \"type\": \"ipv4-address\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the option definition does not exist yet.
+ ASSERT_FALSE(CfgMgr::instance().getOptionDef("isc", 100));
+
+ // Use the configuration string to create new option definitions.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 1);
+}
+
+// The goal of this test is to verify that the option definition
+// comprising an array of uint32 values can be created.
+TEST_F(Dhcp6ParserTest, optionDefArray) {
+
+ // Configuration string. Created option definition should
+ // comprise an array of uint32 values.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"uint32\","
+ " \"array\": True,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Make sure that the particular option definition does not exist.
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("isc", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_UINT32_TYPE, def->getType());
+ EXPECT_TRUE(def->getArrayType());
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid name is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidName) {
+ // Configuration string. The option name is invalid as it
+ // contains the % character.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"invalid%name\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidType) {
+ // Configuration string. The option type is invalid. It is
+ // "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"sting\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+/// The purpose of this test is to verify that the option definition
+/// with invalid type is not accepted.
+TEST_F(Dhcp6ParserTest, optionDefInvalidRecordType) {
+ // Configuration string. The third of the record fields
+ // is invalid. It is "sting" instead of "string".
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"record\","
+ " \"array\": False,"
+ " \"record-types\": \"uint32,uint8,sting\","
+ " \"space\": \"isc\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
+
+/// The purpose of this test is to verify that it is not allowed
+/// to override the standard option (that belongs to dhcp6 option
+/// space) and that it is allowed to define option in the dhcp6
+/// option space that has a code which is not used by any of the
+/// standard options.
+TEST_F(Dhcp6ParserTest, optionStandardDefOverride) {
+
+ // Configuration string. The option code 100 is unassigned
+ // so it can be used for a custom option definition in
+ // dhcp6 option space.
+ std::string config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 100,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ ElementPtr json = Element::fromJSON(config);
+
+ OptionDefinitionPtr def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_FALSE(def);
+
+ // Use the configuration string to create new option definition.
+ ConstElementPtr status;
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // The option definition should now be available in the CfgMgr.
+ def = CfgMgr::instance().getOptionDef("dhcp6", 100);
+ ASSERT_TRUE(def);
+
+ // Check the option data.
+ EXPECT_EQ("foo", def->getName());
+ EXPECT_EQ(100, def->getCode());
+ EXPECT_EQ(OPT_STRING_TYPE, def->getType());
+ EXPECT_FALSE(def->getArrayType());
+
+ // The combination of option space and code is
+ // invalid. The 'dhcp6' option space groups
+ // standard options and the code 3 is reserved
+ // for one of them.
+ config =
+ "{ \"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 3,"
+ " \"type\": \"string\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"dhcp6\""
+ " } ]"
+ "}";
+ json = Element::fromJSON(config);
+
+ // Use the configuration string to create new option definition.
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ // Expecting parsing error (error code 1).
+ checkResult(status, 1);
+}
+
// Goal of this test is to verify that global option
// data is configured for the subnet if the subnet
// configuration does not include options configuration.
@@ -425,14 +928,18 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
"\"rebind-timer\": 2000,"
"\"renew-timer\": 1000,"
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": True"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
@@ -449,42 +956,112 @@ TEST_F(Dhcp6ParserTest, optionDataDefaults) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
// Check that options with other option codes are not returned.
- for (uint16_t code = 102; code < 110; ++code) {
+ for (uint16_t code = 47; code < 57; ++code) {
range = idx.equal_range(code);
EXPECT_EQ(0, std::distance(range.first, range.second));
}
}
-// Goal of this test is to verify options configuration
+/// The goal of this test is to verify that two options having the same
+/// option code can be added to different option spaces.
+TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
+
+ // This configuration string is to configure two options
+ // sharing the code 56 and having different definitions
+ // and belonging to the different option spaces.
+ // The option definition must be provided for the
+ // option that belongs to the 'isc' option space.
+ // The definition is not required for the option that
+ // belongs to the 'dhcp6' option space as it is the
+ // standard option.
+ string config = "{ \"interface\": [ \"all\" ],"
+ "\"rebind-timer\": 2000,"
+ "\"renew-timer\": 1000,"
+ "\"option-data\": [ {"
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
+ " },"
+ " {"
+ " \"name\": \"foo\","
+ " \"space\": \"isc\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": True"
+ " } ],"
+ "\"option-def\": [ {"
+ " \"name\": \"foo\","
+ " \"code\": 38,"
+ " \"type\": \"uint32\","
+ " \"array\": False,"
+ " \"record-types\": \"\","
+ " \"space\": \"isc\""
+ " } ],"
+ "\"subnet6\": [ { "
+ " \"pool\": [ \"2001:db8:1::/80\" ],"
+ " \"subnet\": \"2001:db8:1::/64\""
+ " } ]"
+ "}";
+
+ ConstElementPtr status;
+
+ ElementPtr json = Element::fromJSON(config);
+
+ EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+ ASSERT_TRUE(status);
+ checkResult(status, 0);
+
+ // Options should be now availabe for the subnet.
+ Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
+ ASSERT_TRUE(subnet);
+ // Try to get the option from the space dhcp6.
+ Subnet::OptionDescriptor desc1 = subnet->getOptionDescriptor("dhcp6", 38);
+ ASSERT_TRUE(desc1.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the option from the space isc.
+ Subnet::OptionDescriptor desc2 = subnet->getOptionDescriptor("isc", 38);
+ ASSERT_TRUE(desc2.option);
+ EXPECT_EQ(38, desc1.option->getType());
+ // Try to get the non-existing option from the non-existing
+ // option space and expect that option is not returned.
+ Subnet::OptionDescriptor desc3 = subnet->getOptionDescriptor("non-existing", 38);
+ ASSERT_FALSE(desc3.option);
+}
+
+// The goal of this test is to verify options configuration
// for a single subnet. In particular this test checks
// that local options configuration overrides global
// option setting.
@@ -495,22 +1072,28 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB\","
+ " \"csv-format\": False"
" } ],"
"\"subnet6\": [ { "
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"AB CDEF0105\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"AB CDEF0105\","
+ " \"csv-format\": False"
" },"
" {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"01\""
+ " \"name\": \"preference\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 7,"
+ " \"data\": \"01\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -524,33 +1107,35 @@ TEST_F(Dhcp6ParserTest, optionDataInSingleSubnet) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(2, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(2, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0xAB, 0xCD, 0xEF, 0x01, 0x05
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
- range = idx.equal_range(101);
+ range = idx.equal_range(D6O_PREFERENCE);
ASSERT_EQ(1, std::distance(range.first, range.second));
// Do another round of testing with second option.
- const uint8_t foo2_expected[] = {
+ const uint8_t pref_expected[] = {
0x01
};
- testOption(*range.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range.first, D6O_PREFERENCE, pref_expected,
+ sizeof(pref_expected));
}
// Goal of this test is to verify options configuration
@@ -565,18 +1150,22 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
" \"pool\": [ \"2001:db8:1::/80\" ],"
" \"subnet\": \"2001:db8:1::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo\","
- " \"code\": 100,"
- " \"data\": \"0102030405060708090A\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"0102030405060708090A\","
+ " \"csv-format\": False"
" } ]"
" },"
" {"
" \"pool\": [ \"2001:db8:2::/80\" ],"
" \"subnet\": \"2001:db8:2::/64\", "
" \"option-data\": [ {"
- " \"name\": \"option_foo2\","
- " \"code\": 101,"
- " \"data\": \"FFFEFDFCFB\""
+ " \"name\": \"user-class\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 15,"
+ " \"data\": \"FFFEFDFCFB\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
@@ -590,43 +1179,45 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
Subnet6Ptr subnet1 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet1);
- const Subnet::OptionContainer& options1 = subnet1->getOptions();
- ASSERT_EQ(1, options1.size());
+ Subnet::OptionContainerPtr options1 = subnet1->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options1->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx1 = options1.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx1 = options1->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range1 =
- idx1.equal_range(100);
- // Expect single option with the code equal to 100.
+ idx1.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range1.first, range1.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A
};
// Check if option is valid in terms of code and carried data.
- testOption(*range1.first, 100, foo_expected, sizeof(foo_expected));
+ testOption(*range1.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
// Test another subnet in the same way.
Subnet6Ptr subnet2 = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:2::4"));
ASSERT_TRUE(subnet2);
- const Subnet::OptionContainer& options2 = subnet2->getOptions();
- ASSERT_EQ(1, options2.size());
+ Subnet::OptionContainerPtr options2 = subnet2->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options2->size());
- const Subnet::OptionContainerTypeIndex& idx2 = options2.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx2 = options2->get<1>();
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range2 =
- idx2.equal_range(101);
+ idx2.equal_range(D6O_USER_CLASS);
ASSERT_EQ(1, std::distance(range2.first, range2.second));
- const uint8_t foo2_expected[] = {
+ const uint8_t user_class_expected[] = {
0xFF, 0xFE, 0xFD, 0xFC, 0xFB
};
- testOption(*range2.first, 101, foo2_expected, sizeof(foo2_expected));
+ testOption(*range2.first, D6O_USER_CLASS, user_class_expected,
+ sizeof(user_class_expected));
}
// Verify that empty option name is rejected in the configuration.
@@ -708,25 +1299,26 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
// code so we get the range.
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
- idx.equal_range(80);
- // Expect single option with the code equal to 100.
+ idx.equal_range(D6O_SUBSCRIBER_ID);
+ // Expect single option with the code equal to 38.
ASSERT_EQ(1, std::distance(range.first, range.second));
- const uint8_t foo_expected[] = {
+ const uint8_t subid_expected[] = {
0x0A, 0x0B, 0x0C, 0x0D
};
// Check if option is valid in terms of code and carried data.
- testOption(*range.first, 80, foo_expected, sizeof(foo_expected));
+ testOption(*range.first, D6O_SUBSCRIBER_ID, subid_expected,
+ sizeof(subid_expected));
}
// Verify that specific option object is returned for standard
@@ -734,10 +1326,12 @@ TEST_F(Dhcp6ParserTest, optionDataLowerCase) {
TEST_F(Dhcp6ParserTest, stdOptionData) {
ConstElementPtr x;
std::map<std::string, std::string> params;
- params["name"] = "OPTION_IA_NA";
+ params["name"] = "ia-na";
+ params["space"] = "dhcp6";
// Option code 3 means OPTION_IA_NA.
params["code"] = "3";
- params["data"] = "ABCDEF01 02030405 06070809";
+ params["data"] = "12345, 6789, 1516";
+ params["csv-format"] = "True";
std::string config = createConfigWithOption(params);
ElementPtr json = Element::fromJSON(config);
@@ -749,11 +1343,11 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
Subnet6Ptr subnet = CfgMgr::instance().getSubnet6(IOAddress("2001:db8:1::5"));
ASSERT_TRUE(subnet);
- const Subnet::OptionContainer& options = subnet->getOptions();
- ASSERT_EQ(1, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(1, options->size());
// Get the search index. Index #1 is to search using option code.
- const Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ const Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Get the options for specified index. Expecting one option to be
// returned but in theory we may have multiple options with the same
@@ -778,9 +1372,9 @@ TEST_F(Dhcp6ParserTest, stdOptionData) {
// If cast was successful we may use accessors exposed by
// Option6IA to validate that the content of this option
// has been set correctly.
- EXPECT_EQ(0xABCDEF01, optionIA->getIAID());
- EXPECT_EQ(0x02030405, optionIA->getT1());
- EXPECT_EQ(0x06070809, optionIA->getT2());
+ EXPECT_EQ(12345, optionIA->getIAID());
+ EXPECT_EQ(6789, optionIA->getT1());
+ EXPECT_EQ(1516, optionIA->getT2());
}
};
diff --git a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
index 4c02dde..1df143f 100644
--- a/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
@@ -29,12 +29,13 @@
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/utils.h>
#include <util/buffer.h>
#include <util/range_utilities.h>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
-
+#include <unistd.h>
#include <fstream>
#include <iostream>
#include <sstream>
@@ -59,21 +60,32 @@ public:
using Dhcpv6Srv::processSolicit;
using Dhcpv6Srv::processRequest;
using Dhcpv6Srv::processRenew;
+ using Dhcpv6Srv::processRelease;
using Dhcpv6Srv::createStatusCode;
using Dhcpv6Srv::selectSubnet;
using Dhcpv6Srv::sanityCheck;
+ using Dhcpv6Srv::loadServerID;
+ using Dhcpv6Srv::writeServerID;
};
+static const char* DUID_FILE = "server-id-test.txt";
+
class Dhcpv6SrvTest : public ::testing::Test {
public:
+ /// Name of the server-id file (used in server-id tests)
+
// these are empty for now, but let's keep them around
Dhcpv6SrvTest() : rcode_(-1) {
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 48, 1000,
2000, 3000, 4000));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
+ CfgMgr::instance().deleteSubnets6();
CfgMgr::instance().addSubnet6(subnet_);
+
+ // it's ok if that fails. There should not be such a file anyway
+ unlink(DUID_FILE);
}
// Generate IA_NA option with specified parameters
@@ -143,11 +155,14 @@ public:
}
// Checks that server rejected IA_NA, i.e. that it has no addresses and
- // that expected status code really appears there.
+ // that expected status code really appears there. In some limited cases
+ // (reply to RELEASE) it may be used to verify positive case, where
+ // IA_NA response is expected to not include address.
+ //
// Status code indicates type of error encountered (in theory it can also
// indicate success, but servers typically don't send success status
// as this is the default result and it saves bandwidth)
- void checkRejectedIA_NA(const boost::shared_ptr<Option6IA>& ia,
+ void checkIA_NAStatusCode(const boost::shared_ptr<Option6IA>& ia,
uint16_t expected_status_code) {
// Make sure there is no address assigned.
EXPECT_FALSE(ia->getOption(D6O_IAADDR));
@@ -158,6 +173,12 @@ public:
boost::shared_ptr<OptionCustom> status =
boost::dynamic_pointer_cast<OptionCustom>(ia->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status_code == STATUS_Success && !status) {
+ return;
+ }
+
EXPECT_TRUE(status);
if (status) {
@@ -169,6 +190,26 @@ public:
}
}
+
+ void checkMsgStatusCode(const Pkt6Ptr& msg, uint16_t expected_status) {
+ boost::shared_ptr<OptionCustom> status =
+ boost::dynamic_pointer_cast<OptionCustom>(msg->getOption(D6O_STATUS_CODE));
+
+ // It is ok to not include status success as this is the default behavior
+ if (expected_status == STATUS_Success && !status) {
+ return;
+ }
+
+ EXPECT_TRUE(status);
+ if (status) {
+ // We don't have dedicated class for status code, so let's just interpret
+ // first 2 bytes as status. Remainder of the status code option content is
+ // just a text explanation what went wrong.
+ EXPECT_EQ(static_cast<uint16_t>(expected_status),
+ status->readInteger<uint16_t>(0));
+ }
+ }
+
// Check that generated IAADDR option contains expected address.
void checkIAAddr(const boost::shared_ptr<Option6IAAddr>& addr,
const IOAddress& expected_addr,
@@ -214,6 +255,9 @@ public:
~Dhcpv6SrvTest() {
CfgMgr::instance().deleteSubnets6();
+
+ // Let's clean up if there is such a file.
+ unlink(DUID_FILE);
};
// A subnet used in most tests
@@ -338,25 +382,27 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
" \"pool\": [ \"2001:db8:1::/64\" ],"
" \"subnet\": \"2001:db8:1::/48\", "
" \"option-data\": [ {"
- " \"name\": \"OPTION_DNS_SERVERS\","
+ " \"name\": \"dns-servers\","
+ " \"space\": \"dhcp6\","
" \"code\": 23,"
- " \"data\": \"2001 0DB8 1234 FFFF 0000 0000 0000 0001"
- "2001 0DB8 1234 FFFF 0000 0000 0000 0002\""
+ " \"data\": \"2001:db8:1234:FFFF::1, 2001:db8:1234:FFFF::2\","
+ " \"csv-format\": True"
" },"
" {"
- " \"name\": \"OPTION_FOO\","
- " \"code\": 1000,"
- " \"data\": \"1234\""
+ " \"name\": \"subscriber-id\","
+ " \"space\": \"dhcp6\","
+ " \"code\": 38,"
+ " \"data\": \"1234\","
+ " \"csv-format\": False"
" } ]"
" } ],"
"\"valid-lifetime\": 4000 }";
ElementPtr json = Element::fromJSON(config);
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW(srv.reset(new NakedDhcpv6Srv(0)));
+ NakedDhcpv6Srv srv(0);
- EXPECT_NO_THROW(x = configureDhcp6Server(*srv, json));
+ EXPECT_NO_THROW(x = configureDhcp6Server(srv, json));
ASSERT_TRUE(x);
comment_ = parseAnswer(rcode_, x);
@@ -369,23 +415,23 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- boost::shared_ptr<Pkt6> adv = srv->processSolicit(sol);
+ boost::shared_ptr<Pkt6> adv = srv.processSolicit(sol);
// check if we get response at all
ASSERT_TRUE(adv);
- // We have not requested option with code 1000 so it should not
+ // We have not requested any options so they should not
// be included in the response.
- ASSERT_FALSE(adv->getOption(1000));
+ ASSERT_FALSE(adv->getOption(D6O_SUBSCRIBER_ID));
ASSERT_FALSE(adv->getOption(D6O_NAME_SERVERS));
- // Let's now request option with code 1000.
- // We expect that server will include this option in its reply.
+ // Let's now request some options. We expect that the server
+ // will include them in its response.
boost::shared_ptr<OptionIntArray<uint16_t> >
option_oro(new OptionIntArray<uint16_t>(Option::V6, D6O_ORO));
// Create vector with two option codes.
std::vector<uint16_t> codes(2);
- codes[0] = 1000;
+ codes[0] = D6O_SUBSCRIBER_ID;
codes[1] = D6O_NAME_SERVERS;
// Pass this code to option.
option_oro->setValues(codes);
@@ -393,7 +439,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
sol->addOption(option_oro);
// Need to process SOLICIT again after requesting new option.
- adv = srv->processSolicit(sol);
+ adv = srv.processSolicit(sol);
ASSERT_TRUE(adv);
OptionPtr tmp = adv->getOption(D6O_NAME_SERVERS);
@@ -410,7 +456,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// There is a dummy option with code 1000 we requested from a server.
// Expect that this option is in server's response.
- tmp = adv->getOption(1000);
+ tmp = adv->getOption(D6O_SUBSCRIBER_ID);
ASSERT_TRUE(tmp);
// Check that the option contains valid data (from configuration).
@@ -444,8 +490,7 @@ TEST_F(Dhcpv6SrvTest, advertiseOptions) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
sol->setRemoteAddr(IOAddress("fe80::abcd"));
@@ -454,7 +499,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -467,7 +512,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
checkIAAddr(addr, addr->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -487,8 +532,7 @@ TEST_F(Dhcpv6SrvTest, SolicitBasic) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -505,7 +549,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -521,7 +565,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -541,8 +585,7 @@ TEST_F(Dhcpv6SrvTest, SolicitHint) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a SOLICIT
Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
@@ -557,7 +600,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
sol->addOption(clientid);
// Pass it to the server and get an advertise
- Pkt6Ptr reply = srv->processSolicit(sol);
+ Pkt6Ptr reply = srv.processSolicit(sol);
// check if we get response at all
checkResponse(reply, DHCPV6_ADVERTISE, 1234);
@@ -571,7 +614,7 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
EXPECT_TRUE(subnet_->inPool(addr->getAddress()));
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
}
@@ -580,14 +623,13 @@ TEST_F(Dhcpv6SrvTest, SolicitInvalidHint) {
// This test checks that the server is offering different addresses to different
// clients in ADVERTISEs. Please note that ADVERTISE is not a guarantee that such
-// and address will be assigned. Had the pool was very small and contained only
+// an address will be assigned. Had the pool was very small and contained only
// 2 addresses, the third client would get the same advertise as the first one
// and this is a correct behavior. It is REQUEST that will fail for the third
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManySolicits) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr sol1 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
Pkt6Ptr sol2 = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 2345));
@@ -611,9 +653,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
sol3->addOption(clientid3);
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processSolicit(sol1);
- Pkt6Ptr reply2 = srv->processSolicit(sol2);
- Pkt6Ptr reply3 = srv->processSolicit(sol3);
+ Pkt6Ptr reply1 = srv.processSolicit(sol1);
+ Pkt6Ptr reply2 = srv.processSolicit(sol2);
+ Pkt6Ptr reply3 = srv.processSolicit(sol3);
// check if we get response at all
checkResponse(reply1, DHCPV6_ADVERTISE, 1234);
@@ -634,9 +676,9 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -666,8 +708,7 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
// - server-id
// - IA that includes IAADDR
TEST_F(Dhcpv6SrvTest, RequestBasic) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// Let's create a REQUEST
Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
@@ -684,10 +725,10 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
req->addOption(clientid);
// server-id is mandatory in REQUEST
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRequest(req);
+ Pkt6Ptr reply = srv.processRequest(req);
// check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -703,7 +744,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
checkIAAddr(addr, hint, subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// check that the lease is really in the database
@@ -720,8 +761,7 @@ TEST_F(Dhcpv6SrvTest, RequestBasic) {
// client. ADVERTISE is basically saying "if you send me a request, you will
// probably get an address like this" (there are no guarantees).
TEST_F(Dhcpv6SrvTest, ManyRequests) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr req1 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
Pkt6Ptr req2 = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 2345));
@@ -745,14 +785,14 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
req3->addOption(clientid3);
// server-id is mandatory in REQUEST
- req1->addOption(srv->getServerID());
- req2->addOption(srv->getServerID());
- req3->addOption(srv->getServerID());
+ req1->addOption(srv.getServerID());
+ req2->addOption(srv.getServerID());
+ req3->addOption(srv.getServerID());
// Pass it to the server and get an advertise
- Pkt6Ptr reply1 = srv->processRequest(req1);
- Pkt6Ptr reply2 = srv->processRequest(req2);
- Pkt6Ptr reply3 = srv->processRequest(req3);
+ Pkt6Ptr reply1 = srv.processRequest(req1);
+ Pkt6Ptr reply2 = srv.processRequest(req2);
+ Pkt6Ptr reply3 = srv.processRequest(req3);
// check if we get response at all
checkResponse(reply1, DHCPV6_REPLY, 1234);
@@ -773,9 +813,9 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
checkIAAddr(addr3, addr3->getAddress(), subnet_->getPreferred(), subnet_->getValid());
// check DUIDs
- checkServerId(reply1, srv->getServerID());
- checkServerId(reply2, srv->getServerID());
- checkServerId(reply3, srv->getServerID());
+ checkServerId(reply1, srv.getServerID());
+ checkServerId(reply2, srv.getServerID());
+ checkServerId(reply3, srv.getServerID());
checkClientId(reply1, clientid1);
checkClientId(reply2, clientid2);
checkClientId(reply3, clientid3);
@@ -799,8 +839,7 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
// - 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)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::cafe:babe");
const uint32_t iaid = 234;
@@ -841,10 +880,10 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ req->addOption(srv.getServerID());
// Pass it to the server and hope for a REPLY
- Pkt6Ptr reply = srv->processRenew(req);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, 1234);
@@ -860,7 +899,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
checkIAAddr(addr_opt, addr, subnet_->getPreferred(), subnet_->getValid());
// Check DUIDs
- checkServerId(reply, srv->getServerID());
+ checkServerId(reply, srv.getServerID());
checkClientId(reply, clientid);
// Check that the lease is really in the database
@@ -895,9 +934,7 @@ TEST_F(Dhcpv6SrvTest, RenewBasic) {
// - 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)) );
+ NakedDhcpv6Srv srv(0);
const IOAddress addr("2001:db8:1:1::dead");
const uint32_t transid = 1234;
@@ -925,12 +962,12 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(clientid);
// Server-id is mandatory in RENEW
- req->addOption(srv->getServerID());
+ 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);
+ Pkt6Ptr reply = srv.processRenew(req);
// Check if we get response at all
checkResponse(reply, DHCPV6_REPLY, transid);
@@ -939,7 +976,7 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
// 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);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// Check that there is no lease added
l = LeaseMgrFactory::instance().getLease6(addr);
@@ -956,14 +993,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
// Pass it to the server and hope for a REPLY
- reply = srv->processRenew(req);
+ 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);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
// There is a iaid mis-match, so server should respond that there is
// no such address to renew.
@@ -975,14 +1012,14 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
req->addOption(generateClientId(13)); // generate different DUID
// (with length 13)
- reply = srv->processRenew(req);
+ 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);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
lease = LeaseMgrFactory::instance().getLease6(addr);
ASSERT_TRUE(lease);
@@ -992,10 +1029,198 @@ TEST_F(Dhcpv6SrvTest, RenewReject) {
EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
}
+// This test verifies that incoming (positive) RELEASE can be handled properly,
+// that a REPLY is generated, that the response has status code and that the
+// lease is indeed removed from the database.
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that does not include an IAADDR
+// - lease is actually removed from LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseBasic) {
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::cafe:babe");
+ const uint32_t iaid = 234;
+
+ // Generate client-id also duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the address we are about to use is indeed in pool
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // Note that preferred, valid, T1 and T2 timers and CLTT are set to invalid
+ // value on purpose. They should be updated during RENEW.
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ lease->cltt_ = 1234;
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Check that the lease is really in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, 1234));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RELEASE
+ req->addOption(srv.getServerID());
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, 1234);
+
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ checkIA_NAStatusCode(ia, STATUS_Success);
+ checkMsgStatusCode(reply, STATUS_Success);
+
+ // There should be no address returned in RELEASE (see RFC3315, 18.2.6)
+ EXPECT_FALSE(tmp->getOption(D6O_IAADDR));
+
+ // Check DUIDs
+ checkServerId(reply, srv.getServerID());
+ checkClientId(reply, clientid);
+
+ // Check that the lease is really gone in the database
+ // get lease by address
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // get lease by subnetid/duid/iaid combination
+ l = LeaseMgrFactory::instance().getLease6(*duid_, iaid, subnet_->getID());
+ ASSERT_FALSE(l);
+}
+
+// This test verifies that incoming (invalid) RELEASE can be handled properly.
+//
+// This test checks 3 scenarios:
+// 1. there is no such lease at all
+// 2. there is such a lease, but it is assigned to a different IAID
+// 3. there is such a lease, but it belongs to a different client
+//
+// expected:
+// - returned REPLY message has copy of client-id
+// - returned REPLY message has server-id
+// - returned REPLY message has IA that includes STATUS-CODE
+// - No lease in LeaseMgr
+TEST_F(Dhcpv6SrvTest, ReleaseReject) {
+
+ NakedDhcpv6Srv srv(0);
+
+ const IOAddress addr("2001:db8:1:1::dead");
+ const uint32_t transid = 1234;
+ const uint32_t valid_iaid = 234;
+ const uint32_t bogus_iaid = 456;
+
+ // Quick sanity check that the address we're about to use is ok
+ ASSERT_TRUE(subnet_->inPool(addr));
+
+ // GenerateClientId() also sets duid_
+ OptionPtr clientid = generateClientId();
+
+ // Check that the lease is NOT in the database
+ Lease6Ptr l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // Let's create a RELEASE
+ Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RELEASE, transid));
+ req->setRemoteAddr(IOAddress("fe80::abcd"));
+ boost::shared_ptr<Option6IA> ia = generateIA(bogus_iaid, 1500, 3000);
+
+ OptionPtr released_addr_opt(new Option6IAAddr(D6O_IAADDR, addr, 300, 500));
+ ia->addOption(released_addr_opt);
+ req->addOption(ia);
+ req->addOption(clientid);
+
+ // Server-id is mandatory in RENEW
+ req->addOption(srv.getServerID());
+
+ // Case 1: No lease known to server
+ SCOPED_TRACE("CASE 1: No lease known to server");
+
+ // Pass it to the server and hope for a REPLY
+ Pkt6Ptr reply = srv.processRelease(req);
+
+ // Check if we get response at all
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ OptionPtr tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is not there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_FALSE(l);
+
+ // CASE 2: Lease is known and belongs to this client, but to a different IAID
+ SCOPED_TRACE("CASE 2: Lease is known and belongs to this client, but to a different IAID");
+
+ Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid_, valid_iaid,
+ 501, 502, 503, 504, subnet_->getID(), 0));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Pass it to the server and hope for a REPLY
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // CASE 3: Lease belongs to a client with different client-id
+ SCOPED_TRACE("CASE 3: Lease belongs to a client with different client-id");
+
+ req->delOption(D6O_CLIENTID);
+ ia = boost::dynamic_pointer_cast<Option6IA>(req->getOption(D6O_IA_NA));
+ ia->setIAID(valid_iaid); // Now iaid in renew matches that in leasemgr
+ req->addOption(generateClientId(13)); // generate different DUID
+ // (with length 13)
+
+ reply = srv.processRelease(req);
+ checkResponse(reply, DHCPV6_REPLY, transid);
+ tmp = reply->getOption(D6O_IA_NA);
+ ASSERT_TRUE(tmp);
+ // Check that IA_NA was returned and that there's an address included
+ ia = boost::dynamic_pointer_cast<Option6IA>(tmp);
+ ASSERT_TRUE(ia);
+ checkIA_NAStatusCode(ia, STATUS_NoBinding);
+ checkMsgStatusCode(reply, STATUS_NoBinding);
+
+ // Check that the lease is still there
+ l = LeaseMgrFactory::instance().getLease6(addr);
+ ASSERT_TRUE(l);
+
+ // Finally, let's cleanup the database
+ EXPECT_TRUE(LeaseMgrFactory::instance().deleteLease(addr));
+}
+
// This test verifies if the status code option is generated properly.
TEST_F(Dhcpv6SrvTest, StatusCode) {
- boost::scoped_ptr<NakedDhcpv6Srv> srv;
- ASSERT_NO_THROW( srv.reset(new NakedDhcpv6Srv(0)) );
+ NakedDhcpv6Srv srv(0);
// a dummy content for client-id
uint8_t expected[] = {
@@ -1005,7 +1230,7 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
0x41, 0x42, 0x43, 0x44, 0x45 // string value ABCDE
};
// Create the option.
- OptionPtr status = srv->createStatusCode(3, "ABCDE");
+ OptionPtr status = srv.createStatusCode(3, "ABCDE");
// Allocate an output buffer. We will store the option
// in wire format here.
OutputBuffer buf(sizeof(expected));
@@ -1019,34 +1244,34 @@ TEST_F(Dhcpv6SrvTest, StatusCode) {
// 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)) );
+ NakedDhcpv6Srv srv(0);
Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
- // check that the packets originating from local addresses can be
+ // Set link-local sender address, so appropriate subnet can be
+ // selected for this packet.
pkt->setRemoteAddr(IOAddress("fe80::abcd"));
// client-id is optional for information-request, so
- EXPECT_NO_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL));
+ 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),
+ 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));
+ 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));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::FORBIDDEN));
- pkt->addOption(srv->getServerID());
+ 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));
+ EXPECT_NO_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY));
// sane section ends here, let's do some negative tests as well
@@ -1054,13 +1279,13 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
pkt->addOption(clientid);
// with more than one client-id it should throw, no matter what
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
pkt->delOption(D6O_CLIENTID);
@@ -1069,20 +1294,166 @@ TEST_F(Dhcpv6SrvTest, sanityCheck) {
// 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());
+ 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),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::OPTIONAL),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::OPTIONAL, Dhcpv6Srv::MANDATORY),
RFCViolation);
- EXPECT_THROW(srv->sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
+ EXPECT_THROW(srv.sanityCheck(pkt, Dhcpv6Srv::MANDATORY, Dhcpv6Srv::MANDATORY),
RFCViolation);
+}
+
+// This test verifies if selectSubnet() selects proper subnet for a given
+// source address.
+TEST_F(Dhcpv6SrvTest, selectSubnetAddr) {
+ NakedDhcpv6Srv srv(0);
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ // CASE 1: We have only one subnet defined and we received local traffic.
+ // The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and we received relayed traffic.
+ // We should NOT select it.
+
+ // Identical steps as in case 1, but repeated for clarity
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+ pkt->setRemoteAddr(IOAddress("2001:db8:abcd::2345"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have three subnets defined and we received local traffic.
+ // Nothing should be selected.
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("fe80::abcd"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 4: We have three subnets defined and we received relayed traffic
+ // that came out of subnet 2. We should select subnet2 then
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("2001:db8:2::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet2);
+
+ // CASE 5: We have three subnets defined and we received relayed traffic
+ // that came out of undefined subnet. We should select nothing
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+ pkt->setRemoteAddr(IOAddress("2001:db8:4::baca"));
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
}
+// This test verifies if selectSubnet() selects proper subnet for a given
+// network interface name.
+TEST_F(Dhcpv6SrvTest, selectSubnetIface) {
+ NakedDhcpv6Srv srv(0);
+
+ Subnet6Ptr subnet1(new Subnet6(IOAddress("2001:db8:1::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet2(new Subnet6(IOAddress("2001:db8:2::"), 48, 1, 2, 3, 4));
+ Subnet6Ptr subnet3(new Subnet6(IOAddress("2001:db8:3::"), 48, 1, 2, 3, 4));
+
+ subnet1->setIface("eth0");
+ subnet3->setIface("wifi1");
+
+ // CASE 1: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth0. The only available subnet should be selected
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ Pkt6Ptr pkt = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+ pkt->setIface("eth0");
+
+ Subnet6Ptr selected = srv.selectSubnet(pkt);
+ EXPECT_EQ(selected, subnet1);
+
+ // CASE 2: We have only one subnet defined and it is available via eth0.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1); // just a single subnet
+
+ pkt->setIface("eth1");
+
+ selected = srv.selectSubnet(pkt);
+ EXPECT_FALSE(selected);
+
+ // CASE 3: We have only 3 subnets defined, one over eth0, one remote and
+ // one over wifi1.
+ // Packet came from eth1. We should not select it
+ CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().addSubnet6(subnet1);
+ CfgMgr::instance().addSubnet6(subnet2);
+ CfgMgr::instance().addSubnet6(subnet3);
+
+ pkt->setIface("eth0");
+ EXPECT_EQ(subnet1, srv.selectSubnet(pkt));
+
+ pkt->setIface("eth3"); // no such interface
+ EXPECT_EQ(Subnet6Ptr(), srv.selectSubnet(pkt)); // nothing selected
+
+ pkt->setIface("wifi1");
+ EXPECT_EQ(subnet3, srv.selectSubnet(pkt));
+}
+
+// This test verifies if the server-id disk operations (read, write) are
+// working properly.
+TEST_F(Dhcpv6SrvTest, ServerID) {
+ NakedDhcpv6Srv srv(0);
+
+ string duid1_text = "01:ff:02:03:06:80:90:ab:cd:ef";
+ uint8_t duid1[] = { 0x01, 0xff, 2, 3, 6, 0x80, 0x90, 0xab, 0xcd, 0xef };
+ OptionBuffer expected_duid1(duid1, duid1 + sizeof(duid1));
+
+ fstream file1(DUID_FILE, ios::out | ios::trunc);
+ file1 << duid1_text;
+ file1.close();
+
+ // Test reading from a file
+ EXPECT_TRUE(srv.loadServerID(DUID_FILE));
+ ASSERT_TRUE(srv.getServerID());
+ ASSERT_EQ(sizeof(duid1) + Option::OPTION6_HDR_LEN, srv.getServerID()->len());
+ ASSERT_TRUE(expected_duid1 == srv.getServerID()->getData());
+
+ // Now test writing to a file
+ EXPECT_EQ(0, unlink(DUID_FILE));
+ EXPECT_NO_THROW(srv.writeServerID(DUID_FILE));
+
+ fstream file2(DUID_FILE, ios::in);
+ ASSERT_TRUE(file2.good());
+ string text;
+ file2 >> text;
+ file2.close();
+
+ EXPECT_EQ(duid1_text, text);
+}
+
+/// @todo: Add more negative tests for processX(), e.g. extend sanityCheck() test
+/// to call processX() methods.
+
} // end of anonymous namespace
diff --git a/src/bin/loadzone/.gitignore b/src/bin/loadzone/.gitignore
index 286abba..41e280a 100644
--- a/src/bin/loadzone/.gitignore
+++ b/src/bin/loadzone/.gitignore
@@ -1,4 +1,5 @@
/b10-loadzone
+/b10-loadzone.py
/loadzone.py
/run_loadzone.sh
/b10-loadzone.8
diff --git a/src/bin/loadzone/b10-loadzone.xml b/src/bin/loadzone/b10-loadzone.xml
index d181503..aa14053 100644
--- a/src/bin/loadzone/b10-loadzone.xml
+++ b/src/bin/loadzone/b10-loadzone.xml
@@ -67,7 +67,7 @@
<para>
Some control entries (aka directives) are supported.
- $ORIGIN is followed by a domain name, and sets the the origin
+ $ORIGIN is followed by a domain name, and sets the origin
that will be used for relative domain names in subsequent records.
$INCLUDE is followed by a filename to load.
The previous origin is restored after the file is included.
@@ -224,21 +224,7 @@
<refsect1>
<title>BUGS</title>
<para>
- As of the initial implementation, the underlying library that
- this tool uses does not fully validate the loaded zone; for
- example, loading will succeed even if it doesn't have the SOA or
- NS record at its origin name. Such checks will be implemented
- in a near future version, but until then, the
- <command>b10-loadzone</command> performs the existence of the
- SOA and NS records by itself. However, <command>b10-loadzone</command>
- only warns about it, and does not cancel the load itself.
- If this warning message is produced, it's the user's
- responsibility to fix the errors and reload it. When the
- library is updated with the post load checks, it will be more
- sophisticated and the such zone won't be successfully loaded.
- </para>
- <para>
- There are some other issues noted in the DESCRIPTION section.
+ There are some issues noted in the DESCRIPTION section.
</para>
</refsect1>
</refentry><!--
diff --git a/src/bin/loadzone/loadzone.py.in b/src/bin/loadzone/loadzone.py.in
index 294df55..3ed3b7d 100755
--- a/src/bin/loadzone/loadzone.py.in
+++ b/src/bin/loadzone/loadzone.py.in
@@ -200,29 +200,6 @@ class LoadZoneRunner:
logger.info(LOADZONE_SQLITE3_USING_DEFAULT_CONFIG, default_db_file)
return '{"database_file": "' + default_db_file + '"}'
- def __cancel_create(self):
- '''sqlite3-only hack: delete the zone just created on load failure.
-
- This should eventually be done via generic datasrc API, but right now
- we don't have that interface. Leaving the zone in this situation
- is too bad, so we handle it with a workaround.
-
- '''
- if self._datasrc_type is not 'sqlite3':
- return
-
- import sqlite3 # we need the module only here
- import json
-
- # If we are here, the following should basically succeed; since
- # this is considered a temporary workaround we don't bother to catch
- # and recover rare failure cases.
- dbfile = json.loads(self._datasrc_config)['database_file']
- with sqlite3.connect(dbfile) as conn:
- cur = conn.cursor()
- cur.execute("DELETE FROM zones WHERE name = ?",
- [self._zone_name.to_text()])
-
def _report_progress(self, loaded_rrs):
'''Dump the current progress report to stdout.
@@ -274,36 +251,14 @@ class LoadZoneRunner:
self.__loaded_rrs >= self._report_interval):
sys.stdout.write('\n')
except Exception as ex:
- # release any remaining lock held in the client/loader
- loader, datasrc_client = None, None
+ # release any remaining lock held in the loader
+ loader = None
if created:
- self.__cancel_create()
+ datasrc_client.delete_zone(self._zone_name)
logger.error(LOADZONE_CANCEL_CREATE_ZONE, self._zone_name,
self._zone_class)
raise LoadFailure(str(ex))
- def _post_load_checks(self):
- '''Perform minimal validity checks on the loaded zone.
-
- We do this ourselves because the underlying library currently
- doesn't do any checks. Once the library support post-load validation
- this check should be removed.
-
- '''
- datasrc_client = DataSourceClient(self._datasrc_type,
- self._datasrc_config)
- _, finder = datasrc_client.find_zone(self._zone_name) # should succeed
- result = finder.find(self._zone_name, RRType.SOA())[0]
- if result is not finder.SUCCESS:
- self._post_load_warning('zone has no SOA')
- result = finder.find(self._zone_name, RRType.NS())[0]
- if result is not finder.SUCCESS:
- self._post_load_warning('zone has no NS')
-
- def _post_load_warning(self, msg):
- logger.warn(LOADZONE_POSTLOAD_ISSUE, self._zone_name,
- self._zone_class, msg)
-
def _set_signal_handlers(self):
signal.signal(signal.SIGINT, self._interrupt_handler)
signal.signal(signal.SIGTERM, self._interrupt_handler)
@@ -321,7 +276,6 @@ class LoadZoneRunner:
total_elapsed_txt = "%.2f" % (time.time() - self.__start_time)
logger.info(LOADZONE_DONE, self.__loaded_rrs, self._zone_name,
self._zone_class, total_elapsed_txt)
- self._post_load_checks()
return 0
except BadArgument as ex:
logger.error(LOADZONE_ARGUMENT_ERROR, ex)
diff --git a/src/bin/loadzone/loadzone_messages.mes b/src/bin/loadzone/loadzone_messages.mes
index db79269..ca241b3 100644
--- a/src/bin/loadzone/loadzone_messages.mes
+++ b/src/bin/loadzone/loadzone_messages.mes
@@ -46,14 +46,6 @@ in the zone file. When this happens, the RRs loaded so far are
effectively deleted from the zone, and the old version (if exists)
will still remain valid for operations.
-% LOADZONE_POSTLOAD_ISSUE New version of zone %1/%2 has an issue: %3
-b10-loadzone detected a problem after a successful load of zone:
-either or both of SOA and NS records are missing at the zone origin.
-In the current implementation the load will not be canceled for such
-problems. The operator will need to fix the issues and reload the
-zone; otherwise applications (such as b10-auth) that use this data
-source will not work as expected.
-
% LOADZONE_SQLITE3_USING_DEFAULT_CONFIG Using default configuration with SQLite3 DB file %1
The SQLite3 data source is specified as the data source type without a
data source configuration. b10-loadzone uses the default
diff --git a/src/bin/loadzone/run_loadzone.sh.in b/src/bin/loadzone/run_loadzone.sh.in
index f138e4f..b3d61d3 100755
--- a/src/bin/loadzone/run_loadzone.sh.in
+++ b/src/bin/loadzone/run_loadzone.sh.in
@@ -18,7 +18,7 @@
PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
export PYTHON_EXEC
-PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# If necessary (rare cases), explicitly specify paths to dynamic libraries
diff --git a/src/bin/loadzone/tests/correct/include.db b/src/bin/loadzone/tests/correct/include.db
index a9eeca3..53871bb 100644
--- a/src/bin/loadzone/tests/correct/include.db
+++ b/src/bin/loadzone/tests/correct/include.db
@@ -1,8 +1,6 @@
$ORIGIN include. ; initialize origin
$TTL 300
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.include. hostmaster.include. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/known.test.out b/src/bin/loadzone/tests/correct/known.test.out
index eec692e..377158d 100644
--- a/src/bin/loadzone/tests/correct/known.test.out
+++ b/src/bin/loadzone/tests/correct/known.test.out
@@ -80,6 +80,6 @@ ns5.example.com. 90 IN A 4.4.4.4
comment.example.com. 60 IN SOA ns1.example.com. hostmaster.example.com. 1 43200 900 1814400 7200
comment.example.com. 60 IN NS ns1.example.com.
comment.example.com. 60 IN TXT "Simple text"
-comment.example.com. 60 IN TXT "; No comment"
+comment.example.com. 60 IN TXT "\; No comment"
comment.example.com. 60 IN TXT "Also no comment here"
-comment.example.com. 60 IN TXT "A combination ; see?"
+comment.example.com. 60 IN TXT "A combination \; see?"
diff --git a/src/bin/loadzone/tests/correct/mix1.db b/src/bin/loadzone/tests/correct/mix1.db
index 5bc0a95..059fde7 100644
--- a/src/bin/loadzone/tests/correct/mix1.db
+++ b/src/bin/loadzone/tests/correct/mix1.db
@@ -1,7 +1,5 @@
$ORIGIN mix1.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.mix1. hostmaster.mix1. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/mix2.db b/src/bin/loadzone/tests/correct/mix2.db
index e43b943..e89c2af 100644
--- a/src/bin/loadzone/tests/correct/mix2.db
+++ b/src/bin/loadzone/tests/correct/mix2.db
@@ -1,7 +1,5 @@
$ORIGIN mix2.
-; this needs #2500
-;@ 1 IN SOA ns hostmaster (
-@ 1 IN SOA ns.mix2. hostmaster.mix2. (
+@ 1 IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttl1.db b/src/bin/loadzone/tests/correct/ttl1.db
index fec0813..7f04ff8 100644
--- a/src/bin/loadzone/tests/correct/ttl1.db
+++ b/src/bin/loadzone/tests/correct/ttl1.db
@@ -1,7 +1,5 @@
$ORIGIN ttl1.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.ttl1. hostmaster.ttl1. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttl2.db b/src/bin/loadzone/tests/correct/ttl2.db
index 4705978..b7df040 100644
--- a/src/bin/loadzone/tests/correct/ttl2.db
+++ b/src/bin/loadzone/tests/correct/ttl2.db
@@ -1,7 +1,5 @@
$ORIGIN ttl2.
-; this needs #2500
-;@ 1 IN SOA ns hostmaster (
-@ 1 IN SOA ns.ttl2. hostmaster.ttl2 (
+@ 1 IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/correct/ttlext.db b/src/bin/loadzone/tests/correct/ttlext.db
index f8c83b0..844f452 100644
--- a/src/bin/loadzone/tests/correct/ttlext.db
+++ b/src/bin/loadzone/tests/correct/ttlext.db
@@ -1,7 +1,5 @@
$ORIGIN ttlext.
-; this needs #2500
-;@ IN SOA ns hostmaster (
-@ IN SOA ns.ttlext. hostmaster.ttlext. (
+@ IN SOA ns hostmaster (
1 ; serial
3600
1800
diff --git a/src/bin/loadzone/tests/loadzone_test.py b/src/bin/loadzone/tests/loadzone_test.py
index 4f13c5d..d1ee131 100755
--- a/src/bin/loadzone/tests/loadzone_test.py
+++ b/src/bin/loadzone/tests/loadzone_test.py
@@ -237,7 +237,7 @@ class TestLoadZoneRunner(unittest.TestCase):
self.__check_zone_soa(None, zone_name=Name('example.com'))
def __common_post_load_setup(self, zone_file):
- '''Common setup procedure for post load tests.'''
+ '''Common setup procedure for post load tests which should fail.'''
# replace the LoadZoneRunner's original _post_load_warning() for
# inspection
self.__warnings = []
@@ -248,26 +248,18 @@ class TestLoadZoneRunner(unittest.TestCase):
self.__common_load_setup()
self.__runner._zone_file = zone_file
self.__check_zone_soa(ORIG_SOA_TXT)
- self.__runner._do_load()
- self.__runner._post_load_checks()
+ # It fails because there's problem with the zone data
+ self.assertRaises(LoadFailure, self.__runner._do_load)
def test_load_post_check_fail_soa(self):
'''Load succeeds but warns about missing SOA, should cause warn'''
- self.__common_load_setup()
self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
'/example-nosoa.org.zone')
- self.__check_zone_soa(False)
- self.assertEqual(1, len(self.__warnings))
- self.assertEqual('zone has no SOA', self.__warnings[0])
def test_load_post_check_fail_ns(self):
'''Load succeeds but warns about missing NS, should cause warn'''
- self.__common_load_setup()
self.__common_post_load_setup(LOCAL_TESTDATA_PATH +
'/example-nons.org.zone')
- self.__check_zone_soa(NEW_SOA_TXT)
- self.assertEqual(1, len(self.__warnings))
- self.assertEqual('zone has no NS', self.__warnings[0])
def __interrupt_progress(self, loaded_rrs):
'''A helper emulating a signal in the middle of loading.
diff --git a/src/bin/msgq/Makefile.am b/src/bin/msgq/Makefile.am
index 4244d07..a49b125 100644
--- a/src/bin/msgq/Makefile.am
+++ b/src/bin/msgq/Makefile.am
@@ -4,11 +4,20 @@ pkglibexecdir = $(libexecdir)/@PACKAGE@
pkglibexec_SCRIPTS = b10-msgq
+b10_msgqdir = $(pkgdatadir)
+b10_msgq_DATA = msgq.spec
+
CLEANFILES = b10-msgq msgq.pyc
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+CLEANFILES += $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.pyc
man_MANS = b10-msgq.8
DISTCLEANFILES = $(man_MANS)
-EXTRA_DIST = $(man_MANS) msgq.xml
+EXTRA_DIST = $(man_MANS) msgq.xml msgq_messages.mes msgq.spec
+
+nodist_pylogmessage_PYTHON = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
+pylogmessagedir = $(pyexecdir)/isc/log_messages/
+BUILT_SOURCES = $(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py
if GENERATE_DOCS
@@ -23,6 +32,11 @@ $(man_MANS):
endif
+# Define rule to build logging source files from message file
+$(PYTHON_LOGMSGPKG_DIR)/work/msgq_messages.py : msgq_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message \
+ -d $(PYTHON_LOGMSGPKG_DIR)/work -p $(srcdir)/msgq_messages.mes
+
# this is done here since configure.ac AC_OUTPUT doesn't expand exec_prefix
b10-msgq: msgq.py
$(SED) "s|@@PYTHONPATH@@|@pyexecdir@|" msgq.py >$@
diff --git a/src/bin/msgq/msgq.py.in b/src/bin/msgq/msgq.py.in
index bd13a1c..68c18dc 100755
--- a/src/bin/msgq/msgq.py.in
+++ b/src/bin/msgq/msgq.py.in
@@ -29,34 +29,74 @@ import errno
import time
import select
import random
+import threading
+import isc.config.ccsession
from optparse import OptionParser, OptionValueError
import isc.util.process
+import isc.log
+from isc.log_messages.msgq_messages import *
import isc.cc
isc.util.process.rename()
+isc.log.init("b10-msgq", buffer=True)
+# Logger that is used in the actual msgq handling - startup, shutdown and the
+# poller thread.
+logger = isc.log.Logger("msgq")
+# A separate copy for the master/config thread when the poller thread runs.
+# We use a separate instance, since the logger itself doesn't have to be
+# thread safe.
+config_logger = isc.log.Logger("msgq")
+TRACE_START = logger.DBGLVL_START_SHUT
+TRACE_BASIC = logger.DBGLVL_TRACE_BASIC
+TRACE_DETAIL = logger.DBGLVL_TRACE_DETAIL
+
# This is the version that gets displayed to the user.
# The VERSION string consists of the module name, the module version
# number, and the overall BIND 10 version number (set in configure.ac).
VERSION = "b10-msgq 20110127 (BIND 10 @PACKAGE_VERSION@)"
+# If B10_FROM_BUILD is set in the environment, we use data files
+# from a directory relative to that, otherwise we use the ones
+# installed on the system
+if "B10_FROM_BUILD" in os.environ:
+ SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/msgq"
+else:
+ PREFIX = "@prefix@"
+ DATAROOTDIR = "@datarootdir@"
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+SPECFILE_LOCATION = SPECFILE_PATH + "/msgq.spec"
+
class MsgQReceiveError(Exception): pass
class SubscriptionManager:
- def __init__(self):
+ def __init__(self, cfgmgr_ready):
+ """
+ Initialize the subscription manager.
+ parameters:
+ * cfgmgr_ready: A callable object run once the config manager
+ subscribes. This is a hackish solution, but we can't read
+ the configuration sooner.
+ """
self.subscriptions = {}
+ self.__cfgmgr_ready = cfgmgr_ready
+ self.__cfgmgr_ready_called = False
def subscribe(self, group, instance, socket):
"""Add a subscription."""
target = ( group, instance )
if target in self.subscriptions:
- print("[b10-msgq] Appending to existing target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_APPEND_TARGET, group, instance)
if socket not in self.subscriptions[target]:
self.subscriptions[target].append(socket)
else:
- print("[b10-msgq] Creating new target")
+ logger.debug(TRACE_BASIC, MSGQ_SUBS_NEW_TARGET, group, instance)
self.subscriptions[target] = [ socket ]
+ if group == "ConfigManager" and not self.__cfgmgr_ready_called:
+ logger.debug(TRACE_BASIC, MSGQ_CFGMGR_SUBSCRIBED)
+ self.__cfgmgr_ready_called = True
+ self.__cfgmgr_ready()
def unsubscribe(self, group, instance, socket):
"""Remove the socket from the one specific subscription."""
@@ -124,10 +164,52 @@ class MsgQ:
self.sockets = {}
self.connection_counter = random.random()
self.hostname = socket.gethostname()
- self.subs = SubscriptionManager()
+ self.subs = SubscriptionManager(self.cfgmgr_ready)
self.lnames = {}
self.sendbuffs = {}
self.running = False
+ self.__cfgmgr_ready = None
+ self.__cfgmgr_ready_cond = threading.Condition()
+ # A lock used when the message queue does anything more complicated.
+ # It is mostly a safety measure, the threads doing so should be mostly
+ # independent, and the one with config session should be read only,
+ # but with threads, one never knows. We use threads for concurrency,
+ # not for performance, so we use wide lock scopes to be on the safe
+ # side.
+ self.__lock = threading.Lock()
+
+ def cfgmgr_ready(self, ready=True):
+ """Notify that the config manager is either subscribed, or
+ that the msgq is shutting down and it won't connect, but
+ anybody waiting for it should stop anyway.
+
+ The ready parameter signifies if the config manager is subscribed.
+
+ This method can be called multiple times, but second and any
+ following call is simply ignored. This means the "abort" version
+ of the call can be used on any stop unconditionally, even when
+ the config manager already connected.
+ """
+ with self.__cfgmgr_ready_cond:
+ if self.__cfgmgr_ready is not None:
+ # This is a second call to this method. In that case it does
+ # nothing.
+ return
+ self.__cfgmgr_ready = ready
+ self.__cfgmgr_ready_cond.notify_all()
+
+ def wait_cfgmgr(self):
+ """Wait for msgq to subscribe.
+
+ When this returns, the config manager is either subscribed, or
+ msgq gave up waiting for it. Success is signified by the return
+ value.
+ """
+ with self.__cfgmgr_ready_cond:
+ # Wait until it either aborts or subscribes
+ while self.__cfgmgr_ready is None:
+ self.__cfgmgr_ready_cond.wait()
+ return self.__cfgmgr_ready
def setup_poller(self):
"""Set up the poll thing. Internal function."""
@@ -137,7 +219,7 @@ class MsgQ:
self.poller = select.poll()
def add_kqueue_socket(self, socket, write_filter=False):
- """Add a kquque filter for a socket. By default the read
+ """Add a kqueue filter for a socket. By default the read
filter is used; if write_filter is set to True, the write
filter is used. We use a boolean value instead of a specific
filter constant, because kqueue filter values do not seem to
@@ -162,9 +244,7 @@ class MsgQ:
def setup_listener(self):
"""Set up the listener socket. Internal function."""
- if self.verbose:
- sys.stdout.write("[b10-msgq] Setting up socket at %s\n" %
- self.socket_file)
+ logger.debug(TRACE_BASIC, MSGQ_LISTENER_SETUP, self.socket_file)
self.listen_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
@@ -179,8 +259,7 @@ class MsgQ:
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
self.listen_socket.close()
- sys.stderr.write("[b10-msgq] failed to setup listener on %s: %s\n"
- % (self.socket_file, str(e)))
+ logger.fatal(MSGQ_LISTENER_FAILED, self.socket_file, e)
raise e
if self.poller:
@@ -188,6 +267,20 @@ class MsgQ:
else:
self.add_kqueue_socket(self.listen_socket)
+ def setup_signalsock(self):
+ """Create a socket pair used to signal when we want to finish.
+ Using a socket is easy and thread/signal safe way to signal
+ the termination.
+ """
+ # The __poller_sock will be the end in the poller. When it is
+ # closed, we should shut down.
+ (self.__poller_sock, self.__control_sock) = socket.socketpair()
+
+ if self.poller:
+ self.poller.register(self.__poller_sock, select.POLLIN)
+ else:
+ self.add_kqueue_socket(self.__poller_sock)
+
def setup(self):
"""Configure listener socket, polling, etc.
Raises a socket.error if the socket_file cannot be
@@ -195,10 +288,10 @@ class MsgQ:
"""
self.setup_poller()
+ self.setup_signalsock()
self.setup_listener()
- if self.verbose:
- sys.stdout.write("[b10-msgq] Listening\n")
+ logger.debug(TRACE_START, MSGQ_LISTENER_STARTED);
self.runnable = True
@@ -226,10 +319,9 @@ class MsgQ:
def process_socket(self, fd):
"""Process a read on a socket."""
if not fd in self.sockets:
- sys.stderr.write("[b10-msgq] Got read on Strange Socket fd %d\n" % fd)
+ logger.error(MSGQ_READ_UNKNOWN_FD, fd)
return
sock = self.sockets[fd]
-# sys.stderr.write("[b10-msgq] Got read on fd %d\n" %fd)
self.process_packet(fd, sock)
def kill_socket(self, fd, sock):
@@ -243,7 +335,7 @@ class MsgQ:
del self.sockets[fd]
if fd in self.sendbuffs:
del self.sendbuffs[fd]
- sys.stderr.write("[b10-msgq] Closing socket fd %d\n" % fd)
+ logger.debug(TRACE_BASIC, MSGQ_SOCK_CLOSE, fd)
def getbytes(self, fd, sock, length):
"""Get exactly the requested bytes, or raise an exception if
@@ -285,15 +377,15 @@ class MsgQ:
try:
routing, data = self.read_packet(fd, sock)
except MsgQReceiveError as err:
+ logger.error(MSGQ_RECV_ERR, fd, err)
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Receive error: %s\n" % err)
return
try:
routingmsg = isc.cc.message.from_wire(routing)
except DecodeError as err:
self.kill_socket(fd, sock)
- sys.stderr.write("[b10-msgq] Routing decode error: %s\n" % err)
+ logger.error(MSGQ_HDR_DECODE_ERR, fd, err)
return
self.process_command(fd, sock, routingmsg, data)
@@ -301,9 +393,7 @@ class MsgQ:
def process_command(self, fd, sock, routing, data):
"""Process a single command. This will split out into one of the
other functions."""
- # TODO: A print statement got removed here (one that prints the
- # routing envelope). When we have logging with multiple levels,
- # we might want to re-add that on a high debug verbosity.
+ logger.debug(TRACE_DETAIL, MSGQ_RECV_HDR, routing)
cmd = routing["type"]
if cmd == 'send':
self.process_command_send(sock, routing, data)
@@ -319,7 +409,7 @@ class MsgQ:
elif cmd == 'stop':
self.stop()
else:
- sys.stderr.write("[b10-msgq] Invalid command: %s\n" % cmd)
+ logger.error(MSGQ_INVALID_CMD, cmd)
def preparemsg(self, env, msg = None):
if type(env) == dict:
@@ -363,8 +453,8 @@ class MsgQ:
elif e.errno in [ errno.EPIPE,
errno.ECONNRESET,
errno.ENOBUFS ]:
- print("[b10-msgq] " + errno.errorcode[e.errno] +
- " on send, dropping message and closing connection")
+ logger.error(MSGQ_SEND_ERR, sock.fileno(),
+ errno.errorcode[e.errno])
self.kill_socket(sock.fileno(), sock)
return None
else:
@@ -491,18 +581,23 @@ class MsgQ:
if err.args[0] == errno.EINTR:
events = []
else:
- sys.stderr.write("[b10-msgq] Error with poll(): %s\n" % err)
+ logger.fatal(MSGQ_POLL_ERR, err)
break
- for (fd, event) in events:
- if fd == self.listen_socket.fileno():
- self.process_accept()
- else:
- if event & select.POLLOUT:
- self.__process_write(fd)
- elif event & select.POLLIN:
- self.process_socket(fd)
+ with self.__lock:
+ for (fd, event) in events:
+ if fd == self.listen_socket.fileno():
+ self.process_accept()
+ elif fd == self.__poller_sock.fileno():
+ # If it's the signal socket, we should terminate now.
+ self.running = False
+ break
else:
- print("[b10-msgq] Error: Unknown even in run_poller()")
+ if event & select.POLLOUT:
+ self.__process_write(fd)
+ elif event & select.POLLIN:
+ self.process_socket(fd)
+ else:
+ logger.error(MSGQ_POLL_UNKNOWN_EVENT, fd, event)
def run_kqueue(self):
while self.running:
@@ -512,38 +607,95 @@ class MsgQ:
if not events:
raise RuntimeError('serve: kqueue returned no events')
- for event in events:
- if event.ident == self.listen_socket.fileno():
- self.process_accept()
- else:
- if event.filter == select.KQ_FILTER_WRITE:
- self.__process_write(event.ident)
- if event.filter == select.KQ_FILTER_READ and \
- event.data > 0:
- self.process_socket(event.ident)
- elif event.flags & select.KQ_EV_EOF:
- self.kill_socket(event.ident,
- self.sockets[event.ident])
+ with self.__lock:
+ for event in events:
+ if event.ident == self.listen_socket.fileno():
+ self.process_accept()
+ elif event.ident == self.__poller_sock.fileno():
+ # If it's the signal socket, we should terminate now.
+ self.running = False
+ break;
+ else:
+ if event.filter == select.KQ_FILTER_WRITE:
+ self.__process_write(event.ident)
+ if event.filter == select.KQ_FILTER_READ and \
+ event.data > 0:
+ self.process_socket(event.ident)
+ elif event.flags & select.KQ_EV_EOF:
+ self.kill_socket(event.ident,
+ self.sockets[event.ident])
def stop(self):
- self.running = False
+ # Signal it should terminate.
+ self.__control_sock.close()
+ self.__control_sock = None
+ # Abort anything waiting on the condition, just to make sure it's not
+ # blocked forever
+ self.cfgmgr_ready(False)
+
+ def cleanup_signalsock(self):
+ """Close the signal sockets. We could do it directly in shutdown,
+ but this part is reused in tests.
+ """
+ if self.__poller_sock:
+ self.__poller_sock.close()
+ self.__poller_sock = None
+ if self.__control_sock:
+ self.__control_sock.close()
+ self.__control_sock = None
def shutdown(self):
"""Stop the MsgQ master."""
- if self.verbose:
- sys.stdout.write("[b10-msgq] Stopping the server.\n")
+ logger.debug(TRACE_START, MSGQ_SHUTDOWN)
self.listen_socket.close()
+ self.cleanup_signalsock()
+ # Close all the sockets too. In real life, there should be none now,
+ # as Msgq should be the last one. But some tests don't adhere to this
+ # and create a new Msgq for each test, which led to huge socket leaks.
+ # Some other threads put some other things in instead of sockets, so
+ # we catch whatever exceptions there we can. This should be safe,
+ # because in real operation, we will terminate now anyway, implicitly
+ # closing anything anyway.
+ for sock in self.sockets.values():
+ try:
+ sock.close()
+ except Exception:
+ pass
if os.path.exists(self.socket_file):
os.remove(self.socket_file)
-# can signal handling and calling a destructor be done without a
-# global variable?
-msgq = None
+ def config_handler(self, new_config):
+ """The configuration handler (run in a separate thread).
+ Not tested, currently effectively empty.
+ """
+ config_logger.debug(TRACE_DETAIL, MSGQ_CONFIG_DATA, new_config)
+
+ with self.__lock:
+ if not self.running:
+ return
+
+ # TODO: Any config handlig goes here.
+
+ return isc.config.create_answer(0)
-def signal_handler(signal, frame):
+ def command_handler(self, command, args):
+ """The command handler (run in a separate thread).
+ Not tested, currently effectively empty.
+ """
+ config_logger.debug(TRACE_DETAIL, MSGQ_COMMAND, command, args)
+
+ with self.__lock:
+ if not self.running:
+ return
+
+ # TODO: Any commands go here
+
+ config_logger.error(MSGQ_COMMAND_UNKNOWN, command)
+ return isc.config.create_answer(1, 'unknown command: ' + command)
+
+def signal_handler(msgq, signal, frame):
if msgq:
- msgq.shutdown()
- sys.exit(0)
+ msgq.stop()
if __name__ == "__main__":
def check_port(option, opt_str, value, parser):
@@ -556,6 +708,7 @@ if __name__ == "__main__":
# Parse any command-line options.
parser = OptionParser(version=VERSION)
+ # TODO: Should we remove the option?
parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
help="display more about what is going on")
parser.add_option("-s", "--socket-file", dest="msgq_socket_file",
@@ -563,22 +716,46 @@ if __name__ == "__main__":
help="UNIX domain socket file the msgq daemon will use")
(options, args) = parser.parse_args()
- signal.signal(signal.SIGTERM, signal_handler)
-
# Announce startup.
- if options.verbose:
- sys.stdout.write("[b10-msgq] %s\n" % VERSION)
+ logger.debug(TRACE_START, MSGQ_START, VERSION)
msgq = MsgQ(options.msgq_socket_file, options.verbose)
+ signal.signal(signal.SIGTERM,
+ lambda signal, frame: signal_handler(msgq, signal, frame))
+
try:
msgq.setup()
except Exception as e:
- sys.stderr.write("[b10-msgq] Error on startup: %s\n" % str(e))
+ logger.fatal(MSGQ_START_FAIL, e)
sys.exit(1)
+ # We run the processing in a separate thread. This is because we want to
+ # connect to the msgq ourself. But the cc library is unfortunately blocking
+ # in many places and waiting for the processing part to answer, it would
+ # deadlock.
+ poller_thread = threading.Thread(target=msgq.run)
+ poller_thread.daemon = True
try:
- msgq.run()
+ poller_thread.start()
+ if msgq.wait_cfgmgr():
+ # Once we get the config manager, we can read our own config.
+ session = isc.config.ModuleCCSession(SPECFILE_LOCATION,
+ msgq.config_handler,
+ msgq.command_handler,
+ None, True,
+ msgq.socket_file)
+ session.start()
+ # And we create a thread that'll just wait for commands and
+ # handle them. We don't terminate the thread, we set it to
+ # daemon. Once the main thread terminates, it'll just die.
+ def run_session():
+ while True:
+ session.check_command(False)
+ background_thread = threading.Thread(target=run_session)
+ background_thread.daemon = True
+ background_thread.start()
+ poller_thread.join()
except KeyboardInterrupt:
pass
diff --git a/src/bin/msgq/msgq.spec b/src/bin/msgq/msgq.spec
new file mode 100644
index 0000000..93204fa
--- /dev/null
+++ b/src/bin/msgq/msgq.spec
@@ -0,0 +1,8 @@
+{
+ "module_spec": {
+ "module_name": "Msgq",
+ "module_description": "The message queue",
+ "config_data": [],
+ "commands": []
+ }
+}
diff --git a/src/bin/msgq/msgq_messages.mes b/src/bin/msgq/msgq_messages.mes
new file mode 100644
index 0000000..75e4227
--- /dev/null
+++ b/src/bin/msgq/msgq_messages.mes
@@ -0,0 +1,106 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+# No namespace declaration - these constants go in the global namespace
+# of the ddns messages python module.
+
+# When you add a message to this file, it is a good idea to run
+# <topsrcdir>/tools/reorder_message_file.py to make sure the
+# messages are in the correct order.
+
+% MSGQ_CFGMGR_SUBSCRIBED The config manager subscribed to message queue
+This is a debug message. The message queue has little bit of special handling
+for the configuration manager. This special handling is happening now.
+
+% MSGQ_COMMAND Running command %1 with arguments %2
+Debug message. The message queue received a command and it is running it.
+
+% MSGQ_COMMAND_UNKNOWN Unknown command '%1'
+The message queue received a command from other module, but it doesn't
+recognize it. This is probably either a coding error or inconsistency between
+the message queue version and version of the module.
+
+% MSGQ_CONFIG_DATA Received configuration update for the msgq: %1
+Debug message. The message queue received a configuration update, handling it.
+
+% MSGQ_HDR_DECODE_ERR Error decoding header received from socket %1: %2
+The socket with mentioned file descriptor sent a packet. However, it was not
+possible to decode the routing header of the packet. The packet is ignored.
+This may be caused by a programmer error (one of the components sending invalid
+data) or possibly by incompatible version of msgq and the component (but that's
+unlikely, as the protocol is not changed often).
+
+% MSGQ_LISTENER_FAILED Failed to initialize listener on socket file '%1': %2
+The message queue daemon tried to listen on a file socket (the path is in the
+message), but it failed. The error from the operating system is logged.
+
+% MSGQ_LISTENER_SETUP Starting to listen on socket file '%1'
+Debug message. The listener is trying to open a listening socket.
+
+% MSGQ_LISTENER_STARTED Successfully started to listen
+Debug message. The message queue successfully opened a listening socket and
+waits for incoming connections.
+
+% MSGQ_POLL_ERR Error while polling for events: %1
+A low-level error happened when waiting for events, the error is logged. The
+reason for this varies, but it usually means the system is short on some
+resources.
+
+% MSGQ_POLL_UNKNOWN_EVENT Got an unknown event from the poller for fd %1: %2
+An unknown event got out from the poll() system call. This should generally not
+happen and it is either a programmer error or OS bug. The event is ignored. The
+number noted as the event is the raw encoded value, which might be useful to
+the authors when figuring the problem out.
+
+% MSGQ_READ_UNKNOWN_FD Got read on strange socket %1
+The OS reported a file descriptor is ready to read. But the daemon doesn't know
+the mentioned file descriptor, which is either a programmer error or OS bug.
+The read event is ignored.
+
+% MSGQ_RECV_ERR Error reading from socket %1: %2
+There was a low-level error when reading from a socket. The error is logged and
+the corresponding socket is dropped.
+
+% MSGQ_RECV_HDR Received header: %1
+Debug message. This message includes the whole routing header of a packet.
+
+% MSGQ_INVALID_CMD Received invalid command: %1
+An unknown command listed in the log has been received. It is ignored. This
+indicates either a programmer error (eg. a typo in the command name) or
+incompatible version of a module and message queue daemon.
+
+% MSGQ_SEND_ERR Error while sending to socket %1: %2
+There was a low-level error when sending data to a socket. The error is logged
+and the corresponding socket is dropped.
+
+% MSGQ_SHUTDOWN Stopping Msgq
+Debug message. The message queue is shutting down.
+
+% MSGQ_SOCK_CLOSE Closing socket fd %1
+Debug message. Closing the mentioned socket.
+
+% MSGQ_START Msgq version %1 starting
+Debug message. The message queue is starting up.
+
+% MSGQ_START_FAIL Error during startup: %1
+There was an error during early startup of the daemon. More concrete error is
+in the log. The daemon terminates as a result.
+
+% MSGQ_SUBS_APPEND_TARGET Appending to existing target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription by appending it to already existing
+data structure.
+
+% MSGQ_SUBS_NEW_TARGET Creating new target for subscription to group '%1' for instance '%2'
+Debug message. Creating a new subscription. Also creating a new data structure
+to hold it.
diff --git a/src/bin/msgq/run_msgq.sh.in b/src/bin/msgq/run_msgq.sh.in
index 3e464be..3ab4024 100644
--- a/src/bin/msgq/run_msgq.sh.in
+++ b/src/bin/msgq/run_msgq.sh.in
@@ -20,9 +20,25 @@ export PYTHON_EXEC
MYPATH_PATH=@abs_top_builddir@/src/bin/msgq
-PYTHONPATH=@abs_top_srcdir@/src/lib/python
+PYTHONPATH=@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/python:@abs_top_builddir@/src/lib/log/.libs
export PYTHONPATH
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+SET_ENV_LIBRARY_PATH=@SET_ENV_LIBRARY_PATH@
+if test $SET_ENV_LIBRARY_PATH = yes; then
+ @ENV_LIBRARY_PATH@=@abs_top_builddir@/src/lib/dns/.libs:@abs_top_builddir@/src/lib/dns/python/.libs:@abs_top_builddir@/src/lib/cryptolink/.libs:@abs_top_builddir@/src/lib/cc/.libs:@abs_top_builddir@/src/lib/config/.libs:@abs_top_builddir@/src/lib/log/.libs:@abs_top_builddir@/src/lib/acl/.libs:@abs_top_builddir@/src/lib/util/.libs:@abs_top_builddir@/src/lib/util/io/.libs:@abs_top_builddir@/src/lib/exceptions/.libs:@abs_top_builddir@/src/lib/datasrc/.libs:$@ENV_LIBRARY_PATH@
+ export @ENV_LIBRARY_PATH@
+fi
+
+B10_FROM_SOURCE=@abs_top_srcdir@
+export B10_FROM_SOURCE
+# TODO: We need to do this feature based (ie. no general from_source)
+# But right now we need a second one because some spec files are
+# generated and hence end up under builddir
+B10_FROM_BUILD=@abs_top_builddir@
+export B10_FROM_BUILD
+
BIND10_MSGQ_SOCKET_FILE=@abs_top_builddir@/msgq_socket
export BIND10_MSGQ_SOCKET_FILE
diff --git a/src/bin/msgq/tests/Makefile.am b/src/bin/msgq/tests/Makefile.am
index 50b218b..c9ef5d3 100644
--- a/src/bin/msgq/tests/Makefile.am
+++ b/src/bin/msgq/tests/Makefile.am
@@ -21,6 +21,8 @@ endif
$(LIBRARY_PATH_PLACEHOLDER) \
PYTHONPATH=$(COMMON_PYTHON_PATH):$(abs_top_builddir)/src/bin/msgq \
BIND10_TEST_SOCKET_FILE=$(builddir)/test_msgq_socket.sock \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/msgq/tests/msgq_test.in b/src/bin/msgq/tests/msgq_test.in
deleted file mode 100755
index e3aae89..0000000
--- a/src/bin/msgq/tests/msgq_test.in
+++ /dev/null
@@ -1,28 +0,0 @@
-#! /bin/sh
-
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-PYTHON_EXEC=${PYTHON_EXEC:- at PYTHON@}
-export PYTHON_EXEC
-
-MYPATH_PATH=@abs_top_srcdir@/src/bin/msgq/tests
-
-PYTHONPATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/lib/python
-
-export PYTHONPATH
-
-cd ${MYPATH_PATH}
-exec ${PYTHON_EXEC} -O msgq_test.py $*
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 4060190..00e15d8 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -10,6 +10,7 @@ import errno
import threading
import isc.cc
import collections
+import isc.log
#
# Currently only the subscription part and some sending is implemented...
@@ -18,7 +19,12 @@ import collections
class TestSubscriptionManager(unittest.TestCase):
def setUp(self):
- self.sm = SubscriptionManager()
+ self.__cfgmgr_ready_called = 0
+ self.sm = SubscriptionManager(self.cfgmgr_ready)
+
+ def cfgmgr_ready(self):
+ # Called one more time
+ self.__cfgmgr_ready_called += 1
def test_subscription_add_delete_manager(self):
self.sm.subscribe("a", "*", 'sock1')
@@ -100,7 +106,7 @@ class TestSubscriptionManager(unittest.TestCase):
try:
msgq.setup()
self.assertTrue(os.path.exists(socket_file))
- msgq.shutdown();
+ msgq.shutdown()
self.assertFalse(os.path.exists(socket_file))
except socket.error:
# ok, the install path doesn't exist at all,
@@ -114,6 +120,25 @@ class TestSubscriptionManager(unittest.TestCase):
def test_open_socket_bad(self):
msgq = MsgQ("/does/not/exist")
self.assertRaises(socket.error, msgq.setup)
+ # But we can clean up after that.
+ msgq.shutdown()
+
+ def test_subscribe_cfgmgr(self):
+ """Test special handling of the config manager. Once it subscribes,
+ the message queue needs to connect and read the config. But not
+ before and only once.
+ """
+ self.assertEqual(0, self.__cfgmgr_ready_called)
+ # Not called when something else subscribes
+ self.sm.subscribe('SomethingElse', '*', 's1')
+ self.assertEqual(0, self.__cfgmgr_ready_called)
+ # Called whenever the config manager subscribes
+ self.sm.subscribe('ConfigManager', '*', 's2')
+ self.assertEqual(1, self.__cfgmgr_ready_called)
+ # But not called again when it subscribes again (should not
+ # happen in practice, but we make sure anyway)
+ self.sm.subscribe('ConfigManager', '*', 's3')
+ self.assertEqual(1, self.__cfgmgr_ready_called)
class DummySocket:
"""
@@ -193,7 +218,6 @@ class MsgQThread(threading.Thread):
def stop(self):
self.msgq_.stop()
-
class SendNonblock(unittest.TestCase):
"""
Tests that the whole thing will not get blocked if someone does not read.
@@ -281,8 +305,10 @@ class SendNonblock(unittest.TestCase):
if queue_pid == 0:
signal.alarm(120)
msgq.setup_poller()
+ msgq.setup_signalsock()
msgq.register_socket(queue)
msgq.run()
+ msgq.cleanup_signalsock()
else:
try:
def killall(signum, frame):
@@ -356,6 +382,7 @@ class SendNonblock(unittest.TestCase):
# Don't need a listen_socket
msgq.listen_socket = DummySocket
msgq.setup_poller()
+ msgq.setup_signalsock()
msgq.register_socket(write)
msgq.register_socket(control_write)
# Queue the message for sending
@@ -383,6 +410,10 @@ class SendNonblock(unittest.TestCase):
# Fail the test if it didn't stop
self.assertFalse(msgq_thread.isAlive(), "Thread did not stop")
+ # Clean up some internals of msgq (usually called as part of
+ # shutdown, but we skip that one here)
+ msgq.cleanup_signalsock()
+
# Check the exception from the thread, if any
# First, if we didn't expect it; reraise it (to make test fail and
# show the stacktrace for debugging)
@@ -455,6 +486,81 @@ class SendNonblock(unittest.TestCase):
self.do_send_with_send_error(3, sockerr, False, sockerr)
self.do_send_with_send_error(23, sockerr, False, sockerr)
+class ThreadTests(unittest.TestCase):
+ """Test various things around thread synchronization."""
+
+ def setUp(self):
+ self.__msgq = MsgQ()
+ self.__abort_wait = False
+ self.__result = None
+ self.__notify_thread = threading.Thread(target=self.__notify)
+ self.__wait_thread = threading.Thread(target=self.__wait)
+ # Make sure the threads are killed if left behind by the test.
+ self.__notify_thread.daemon = True
+ self.__wait_thread.daemon = True
+
+ def __notify(self):
+ """Call the cfgmgr_ready."""
+ if self.__abort_wait:
+ self.__msgq.cfgmgr_ready(False)
+ else:
+ self.__msgq.cfgmgr_ready()
+
+ def __wait(self):
+ """Wait for config manager and store the result."""
+ self.__result = self.__msgq.wait_cfgmgr()
+
+ def test_wait_cfgmgr(self):
+ """One thread signals the config manager subscribed, the other
+ waits for it. We then check it terminated correctly.
+ """
+ self.__notify_thread.start()
+ self.__wait_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertTrue(self.__result)
+
+ def test_wait_cfgmgr_2(self):
+ """Same as test_wait_cfgmgr, but starting the threads in reverse order
+ (the result should be the same).
+ """
+ self.__wait_thread.start()
+ self.__notify_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertTrue(self.__result)
+
+ def test_wait_abort(self):
+ """Similar to test_wait_cfgmgr, but the config manager is never
+ subscribed and it is aborted.
+ """
+ self.__abort_wait = True
+ self.__wait_thread.start()
+ self.__notify_thread.start()
+ # Timeout to ensure the test terminates even on failure
+ self.__wait_thread.join(60)
+ self.assertIsNotNone(self.__result)
+ self.assertFalse(self.__result)
+
+ def __check_ready_and_abort(self):
+ """Check that when we first say the config manager is ready and then
+ try to abort, it uses the first result.
+ """
+ self.__msgq.cfgmgr_ready()
+ self.__msgq.cfgmgr_ready(False)
+ self.__result = self.__msgq.wait_cfgmgr()
+
+ def test_ready_and_abort(self):
+ """Perform the __check_ready_and_abort test, but in a separate thread,
+ so in case something goes wrong with the synchronisation and it
+ deadlocks, the test will terminate anyway.
+ """
+ test_thread = threading.Thread(target=self.__check_ready_and_abort)
+ test_thread.daemon = True
+ test_thread.start()
+ test_thread.join(60)
+ self.assertTrue(self.__result)
if __name__ == '__main__':
+ isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index cc0f09f..725aa85 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -200,14 +200,14 @@ public:
/**
* \short Get info about timeouts.
*
- * \returns Timeout and retries (as described in setTimeouts).
+ * \return Timeout and retries (as described in setTimeouts).
*/
std::pair<int, unsigned> getTimeouts() const;
/**
* \brief Get the timeout for outgoing queries
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getQueryTimeout() const;
@@ -218,7 +218,7 @@ public:
* (internal resolving on the query will continue, see
* \c getLookupTimeout())
*
- * \returns Timeout for outgoing queries
+ * \return Timeout for outgoing queries
*/
int getClientTimeout() const;
diff --git a/src/bin/stats/tests/b10-stats-httpd_test.py b/src/bin/stats/tests/b10-stats-httpd_test.py
index 997ac41..fb0510a 100644
--- a/src/bin/stats/tests/b10-stats-httpd_test.py
+++ b/src/bin/stats/tests/b10-stats-httpd_test.py
@@ -42,6 +42,7 @@ except ImportError:
lxml_etree = None
import isc
+import isc.log
import stats_httpd
import stats
from test_utils import BaseModules, ThreadingServerManager, MyStats,\
@@ -1066,4 +1067,5 @@ class TestStatsHttpd(unittest.TestCase):
imp.reload(stats_httpd)
if __name__ == "__main__":
+ isc.log.resetUnitTestRootLogger()
unittest.main()
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index 193f46c..d18abf1 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -29,6 +29,7 @@ import time
import imp
import stats
+import isc.log
import isc.cc.session
from test_utils import BaseModules, ThreadingServerManager, MyStats, SignalHandler, send_command, send_shutdown
from isc.testutils.ccsession_mock import MockModuleCCSession
@@ -1254,8 +1255,6 @@ class TestOSEnv(unittest.TestCase):
os.environ["B10_FROM_SOURCE"] = path
imp.reload(stats)
-def test_main():
- unittest.main()
-
if __name__ == "__main__":
- test_main()
+ isc.log.resetUnitTestRootLogger()
+ unittest.main()
diff --git a/src/bin/stats/tests/test_utils.py b/src/bin/stats/tests/test_utils.py
index 96f7046..5c1855a 100644
--- a/src/bin/stats/tests/test_utils.py
+++ b/src/bin/stats/tests/test_utils.py
@@ -103,20 +103,9 @@ class ThreadingServerManager:
else:
self.server._thread.join(0) # timeout is 0
-def do_nothing(*args, **kwargs): pass
-
-class dummy_sys:
- """Dummy for sys"""
- class dummy_io:
- write = do_nothing
- stdout = stderr = dummy_io()
-
class MockMsgq:
def __init__(self):
self._started = threading.Event()
- # suppress output to stdout and stderr
- msgq.sys = dummy_sys()
- msgq.print = do_nothing
self.msgq = msgq.MsgQ(verbose=False)
result = self.msgq.setup()
if result:
@@ -124,10 +113,15 @@ class MockMsgq:
def run(self):
self._started.set()
- self.msgq.run()
+ try:
+ self.msgq.run()
+ finally:
+ # Make sure all the sockets, etc, are removed once it stops.
+ self.msgq.shutdown()
def shutdown(self):
- self.msgq.shutdown()
+ # Ask it to terminate nicely
+ self.msgq.stop()
class MockCfgmgr:
def __init__(self):
@@ -554,15 +548,20 @@ class BaseModules:
def shutdown(self):
+ # MockMsgq. We need to wait (blocking) for it, otherwise it'll wipe out
+ # a socket for another test during its shutdown.
+ self.msgq.shutdown(True)
+
+ # We also wait for the others, but these are just so we don't create
+ # too many threads in parallel.
+
# MockAuth
- self.auth2.shutdown()
- self.auth.shutdown()
+ self.auth2.shutdown(True)
+ self.auth.shutdown(True)
# MockBoss
- self.boss.shutdown()
+ self.boss.shutdown(True)
# MockCfgmgr
- self.cfgmgr.shutdown()
- # MockMsgq
- self.msgq.shutdown()
+ self.cfgmgr.shutdown(True)
# remove the unused socket file
socket_file = self.msgq.server.msgq.socket_file
try:
diff --git a/src/bin/sysinfo/run_sysinfo.sh.in b/src/bin/sysinfo/run_sysinfo.sh.in
index 268b5a4..6459c2d 100755
--- a/src/bin/sysinfo/run_sysinfo.sh.in
+++ b/src/bin/sysinfo/run_sysinfo.sh.in
@@ -20,10 +20,11 @@ export PYTHON_EXEC
SYSINFO_PATH=@abs_top_builddir@/src/bin/sysinfo
-# Note: we shouldn't need log_messages except for the seemingly necessary
-# dependency due to the automatic import in the isc package (its __init__.py
-# imports some other modules)
-PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages
+# Note: we shouldn't need log_messages and lib/dns except for the seemingly
+# necessary dependency due to the automatic import in the isc package (its
+# __init__.py imports some other modules)
+# #2145 should eliminate the need for them.
+PYTHONPATH=@abs_top_builddir@/src/lib/python:@abs_top_srcdir@/src/lib/python:@abs_top_builddir@/src/lib/python/isc/log_messages:@abs_top_builddir@/src/lib/dns/python/.libs
export PYTHONPATH
# Likewise, we need only because isc.log requires some loadable modules.
diff --git a/src/bin/xfrin/tests/xfrin_test.py b/src/bin/xfrin/tests/xfrin_test.py
index b7fe056..99c5e1e 100644
--- a/src/bin/xfrin/tests/xfrin_test.py
+++ b/src/bin/xfrin/tests/xfrin_test.py
@@ -60,7 +60,7 @@ TSIG_KEY = TSIGKey("example.com:SFuWd/q99SzF8Yzd1QbB9g==")
# SOA intended to be used for the new SOA as a result of transfer.
soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1234 3600 1800 2419200 7200')
soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
soa_rrset.add_rdata(soa_rdata)
@@ -68,7 +68,7 @@ soa_rrset.add_rdata(soa_rdata)
# SOA intended to be used for the current SOA at the secondary side.
# Note that its serial is smaller than that of soa_rdata.
begin_soa_rdata = Rdata(RRType.SOA(), TEST_RRCLASS,
- 'master.example.com. admin.example.com ' +
+ 'master.example.com. admin.example.com. ' +
'1230 3600 1800 2419200 7200')
begin_soa_rrset = RRset(TEST_ZONE_NAME, TEST_RRCLASS, RRType.SOA(), RRTTL(3600))
begin_soa_rrset.add_rdata(begin_soa_rdata)
diff --git a/src/bin/xfrin/xfrin_messages.mes b/src/bin/xfrin/xfrin_messages.mes
index 837cafa..770a8b2 100644
--- a/src/bin/xfrin/xfrin_messages.mes
+++ b/src/bin/xfrin/xfrin_messages.mes
@@ -24,7 +24,7 @@ The serial fields of the first and last SOAs of AXFR (including AXFR-style
IXFR) are not the same. According to RFC 5936 these two SOAs must be the
"same" (not only for the serial), but it is still not clear what the
receiver should do if this condition does not hold. There was a discussion
-about this at the IETF dnsext wg:
+about this at the IETF dnsext working group:
http://www.ietf.org/mail-archive/web/dnsext/current/msg07908.html
and the general feeling seems that it would be better to reject the
transfer if a mismatch is detected. On the other hand, also as noted
@@ -61,10 +61,10 @@ There was an error opening a connection to the master. The error is
shown in the log message.
% XFRIN_GOT_INCREMENTAL_RESP got incremental response for %1
-In an attempt of IXFR processing, the begenning SOA of the first difference
+In an attempt of IXFR processing, the beginning SOA of the first difference
(following the initial SOA that specified the final SOA for all the
differences) was found. This means a connection for xfrin tried IXFR
-and really aot a response for incremental updates.
+and really got a response for incremental updates.
% XFRIN_GOT_NONINCREMENTAL_RESP got nonincremental response for %1
Non incremental transfer was detected at the "first data" of a transfer,
@@ -149,16 +149,16 @@ daemon will now shut down.
The AXFR transfer of the given zone was successful.
The provided information contains the following values:
-messages: Number of overhead DNS messages in the transfer
+messages: Number of overhead DNS messages in the transfer.
records: Number of Resource Records in the full transfer, excluding the
final SOA record that marks the end of the AXFR.
bytes: Full size of the transfer data on the wire.
-run time: Time (in seconds) the complete axfr took
+run time: Time (in seconds) the complete axfr took.
-bytes/second: Transfer speed
+bytes/second: Transfer speed.
% XFRIN_TSIG_KEY_NOT_FOUND TSIG key not found in key ring: %1
An attempt to start a transfer with TSIG was made, but the configured TSIG
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
index 6e3f4a8..774187f 100644
--- a/src/bin/xfrout/tests/xfrout_test.py.in
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -249,7 +249,7 @@ class TestXfroutSessionBase(unittest.TestCase):
# In the RDATA only the serial matters.
for i in range(0, num_soa):
soa.add_rdata(Rdata(RRType.SOA(), soa_class,
- 'm r ' + str(ixfr) + ' 1 1 1 1'))
+ 'm. r. ' + str(ixfr) + ' 1 1 1 1'))
msg.add_rrset(Message.SECTION_AUTHORITY, soa)
renderer = MessageRenderer()
diff --git a/src/bin/xfrout/xfrout_messages.mes b/src/bin/xfrout/xfrout_messages.mes
index 97b7a31..d48aa24 100644
--- a/src/bin/xfrout/xfrout_messages.mes
+++ b/src/bin/xfrout/xfrout_messages.mes
@@ -70,7 +70,7 @@ AXFR-style IXFR.
% XFROUT_IXFR_NO_ZONE IXFR client %1, %2: zone not found with journal
The requested zone in IXFR was not found in the data source
-even though the xfrout daemon sucessfully found the SOA RR of the zone
+even though the xfrout daemon successfully found the SOA RR of the zone
in the data source. This can happen if the administrator removed the
zone from the data source within the small duration between these
operations, but it's more likely to be a bug or broken data source.
@@ -84,9 +84,6 @@ NOTAUTH.
An IXFR request was received, but the client's SOA version is the same as
or newer than that of the server. The xfrout server responds to the
request with the answer section being just one SOA of that version.
-Note: as of this wrting the 'newer version' cannot be identified due to
-the lack of support for the serial number arithmetic. This will soon
-be implemented.
% XFROUT_MODULECC_SESSION_ERROR error encountered by configuration/command module: %1
There was a problem in the lower level module handling configuration and
@@ -206,7 +203,7 @@ xfrout daemon process is still running. This xfrout daemon (the one
printing this message) will not start.
% XFROUT_XFR_TRANSFER_CHECK_ERROR %1 client %2: check for transfer of %3 failed: %4
-Pre-response check for an incomding XFR request failed unexpectedly.
+Pre-response check for an incoming XFR request failed unexpectedly.
The most likely cause of this is that some low level error in the data
source, but it may also be other general (more unlikely) errors such
as memory shortage. Some detail of the error is also included in the
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index bb84ae2..db25d9f 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -109,8 +109,7 @@ public:
/// \name pure virtuals, every derived class must implement these
- /// \returns true if the other ElementPtr has the same type and
- /// value
+ /// \return true if the other ElementPtr has the same type and value
virtual bool equals(const Element& other) const = 0;
/// Converts the Element to JSON format and appends it to
diff --git a/src/lib/config/config_data.cc b/src/lib/config/config_data.cc
index ebe51cc..fb5dd75 100644
--- a/src/lib/config/config_data.cc
+++ b/src/lib/config/config_data.cc
@@ -235,7 +235,7 @@ ConfigData::getItemList(const std::string& identifier, bool recurse) const {
ConstElementPtr
ConfigData::getFullConfig() const {
ElementPtr result = Element::createMap();
- ConstElementPtr items = getItemList("", true);
+ ConstElementPtr items = getItemList("", false);
BOOST_FOREACH(ConstElementPtr item, items->listValue()) {
result->set(item->stringValue(), getValue(item->stringValue()));
}
diff --git a/src/lib/config/config_data.h b/src/lib/config/config_data.h
index 0bb1bfd..e40600d 100644
--- a/src/lib/config/config_data.h
+++ b/src/lib/config/config_data.h
@@ -93,8 +93,8 @@ public:
void setLocalConfig(isc::data::ElementPtr config) { _config = config; }
/// Returns the local (i.e. non-default) configuration.
- /// \returns An ElementPtr pointing to a MapElement containing all
- /// non-default configuration options.
+ /// \return An ElementPtr pointing to a MapElement containing all
+ /// non-default configuration options.
isc::data::ElementPtr getLocalConfig() { return (_config); }
/// Returns a list of all possible configuration options as specified
@@ -110,11 +110,11 @@ public:
isc::data::ConstElementPtr getItemList(const std::string& identifier = "",
bool recurse = false) const;
- /// Returns all current configuration settings (both non-default and default).
+ /// Returns a map of the top-level configuration items, as currently
+ /// set or their defaults
+ ///
/// \return An ElementPtr pointing to a MapElement containing
- /// string->value elements, where the string is the
- /// full identifier of the configuration option and the
- /// value is an ElementPtr with the value.
+ /// the top-level configuration items
isc::data::ConstElementPtr getFullConfig() const;
private:
@@ -126,6 +126,6 @@ private:
}
#endif
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/config/tests/config_data_unittests.cc b/src/lib/config/tests/config_data_unittests.cc
index 26a3fc6..4b83e5c 100644
--- a/src/lib/config/tests/config_data_unittests.cc
+++ b/src/lib/config/tests/config_data_unittests.cc
@@ -118,7 +118,7 @@ TEST(ConfigData, getLocalConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
EXPECT_EQ("{ }", cd.getLocalConfig()->str());
-
+
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
EXPECT_EQ("{ \"item1\": 2 }", cd.getLocalConfig()->str());
@@ -141,12 +141,15 @@ TEST(ConfigData, getFullConfig) {
ModuleSpec spec2 = moduleSpecFromFile(std::string(TEST_DATA_PATH) + "/spec2.spec");
ConfigData cd = ConfigData(spec2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config = Element::fromJSON("{ \"item1\": 2 }");
cd.setLocalConfig(my_config);
- EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"default\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 2, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { } }", cd.getFullConfig()->str());
ElementPtr my_config2 = Element::fromJSON("{ \"item6\": { \"value1\": \"a\" } }");
cd.setLocalConfig(my_config2);
- EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6/value1\": \"a\", \"item6/value2\": None }", cd.getFullConfig()->str());
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value1\": \"a\" } }", cd.getFullConfig()->str());
+ ElementPtr my_config3 = Element::fromJSON("{ \"item6\": { \"value2\": 123 } }");
+ cd.setLocalConfig(my_config3);
+ EXPECT_EQ("{ \"item1\": 1, \"item2\": 1.1, \"item3\": true, \"item4\": \"test\", \"item5\": [ \"a\", \"b\" ], \"item6\": { \"value2\": 123 } }", cd.getFullConfig()->str());
}
diff --git a/src/lib/datasrc/Makefile.am b/src/lib/datasrc/Makefile.am
index a4edd4e..dc1007a 100644
--- a/src/lib/datasrc/Makefile.am
+++ b/src/lib/datasrc/Makefile.am
@@ -38,6 +38,7 @@ libb10_datasrc_la_SOURCES += client_list.h client_list.cc
libb10_datasrc_la_SOURCES += memory_datasrc.h memory_datasrc.cc
libb10_datasrc_la_SOURCES += master_loader_callbacks.h
libb10_datasrc_la_SOURCES += master_loader_callbacks.cc
+libb10_datasrc_la_SOURCES += rrset_collection_base.h rrset_collection_base.cc
libb10_datasrc_la_SOURCES += zone_loader.h zone_loader.cc
nodist_libb10_datasrc_la_SOURCES = datasrc_messages.h datasrc_messages.cc
libb10_datasrc_la_LDFLAGS = -no-undefined -version-info 1:0:1
diff --git a/src/lib/datasrc/client.cc b/src/lib/datasrc/client.cc
index 919e9ab..c69b23c 100644
--- a/src/lib/datasrc/client.cc
+++ b/src/lib/datasrc/client.cc
@@ -28,20 +28,22 @@ namespace datasrc {
ZoneIteratorPtr
DataSourceClient::getIterator(const isc::dns::Name&, bool) const {
- isc_throw(isc::NotImplemented,
- "Data source doesn't support iteration");
+ 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");
+ 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");
+ isc_throw(isc::NotImplemented, "Data source doesn't support createZone");
+}
+
+bool
+DataSourceClient::deleteZone(const dns::Name&) {
+ isc_throw(isc::NotImplemented, "Data source doesn't support deleteZone");
}
} // end namespace datasrc
diff --git a/src/lib/datasrc/client.h b/src/lib/datasrc/client.h
index 8739489..607af05 100644
--- a/src/lib/datasrc/client.h
+++ b/src/lib/datasrc/client.h
@@ -385,9 +385,41 @@ public:
/// 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
+ /// \param zone_name The (fully qualified) name of the zone to create
/// \return True if the zone was added, false if it already existed
- virtual bool createZone(const dns::Name& name);
+ virtual bool createZone(const dns::Name& zone_name);
+
+ /// \brief Delete a zone from the data source
+ ///
+ /// This method also checks if the specified zone exists in the data
+ /// source, and returns true/false depending on whether the zone
+ /// existed/not existed, respectively. In either case, on successful
+ /// return it ensures the data source does not contain the specified
+ /// name of the zone.
+ ///
+ /// \note This is a tentative API, and this method is likely to change
+ /// or be removed in the near future. For that reason, it currently
+ /// provides a default implementation that throws NotImplemented.
+ /// Note also that this method does not delete other database records
+ /// related to the zone, such as zone's resource records or differences
+ /// corresponding to updates made in the zone. This is primarily for
+ /// implementation simplicity (in the currently intended usage there
+ /// wouldn't be such other data at the time of this call anyway) and due
+ /// to the fact that details of managing zones is still in flux. Once
+ /// the design in this area is fixed we may revisit the behavior.
+ ///
+ /// Apart from the two exceptions mentioned below, in theory this
+ /// call can throw anything, depending on the implementation of
+ /// the datasource backend.
+ ///
+ /// \throw NotImplemented If the datasource backend does not support
+ /// direct zone deletion.
+ /// \throw DataSourceError If something goes wrong in the data source
+ /// while deleting the zone.
+ /// \param zone_name The (fully qualified) name of the zone to be deleted
+ /// \return true if the zone previously existed and has been deleted by
+ /// this method; false if the zone didn't exist.
+ virtual bool deleteZone(const dns::Name& zone_name);
};
}
}
diff --git a/src/lib/datasrc/database.cc b/src/lib/datasrc/database.cc
index d791fbc..0b010a4 100644
--- a/src/lib/datasrc/database.cc
+++ b/src/lib/datasrc/database.cc
@@ -19,6 +19,7 @@
#include <datasrc/database.h>
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
+#include <datasrc/rrset_collection_base.h>
#include <exceptions/exceptions.h>
#include <dns/name.h>
@@ -117,13 +118,25 @@ DatabaseClient::findZone(const Name& name) const {
}
bool
-DatabaseClient::createZone(const Name& name) {
+DatabaseClient::createZone(const Name& zone_name) {
TransactionHolder transaction(*accessor_);
- std::pair<bool, int> zone(accessor_->getZone(name.toText()));
+ const std::pair<bool, int> zone(accessor_->getZone(zone_name.toText()));
if (zone.first) {
return (false);
}
- accessor_->addZone(name.toText());
+ accessor_->addZone(zone_name.toText());
+ transaction.commit();
+ return (true);
+}
+
+bool
+DatabaseClient::deleteZone(const Name& zone_name) {
+ TransactionHolder transaction(*accessor_);
+ const std::pair<bool, int> zinfo(accessor_->getZone(zone_name.toText()));
+ if (!zinfo.first) { // if it doesn't exist just return false
+ return (false);
+ }
+ accessor_->deleteZone(zinfo.second);
transaction.commit();
return (true);
}
@@ -1372,6 +1385,36 @@ DatabaseClient::getIterator(const isc::dns::Name& name,
return (iterator);
}
+/// \brief datasrc implementation of RRsetCollectionBase.
+class RRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ RRsetCollection(ZoneUpdater& updater, const isc::dns::RRClass& rrclass) :
+ isc::datasrc::RRsetCollectionBase(updater, rrclass)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief A wrapper around \c disable() so that it can be used as a
+ /// public method. \c disable() is protected.
+ void disableWrapper() {
+ disable();
+ }
+
+protected:
+ // TODO: RRsetCollectionBase::Iter is not implemented and the
+ // following two methods just throw.
+
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ isc_throw(NotImplemented, "This method is not implemented.");
+ }
+};
+
//
// Zone updater using some database system as the underlying data source.
//
@@ -1411,6 +1454,15 @@ public:
virtual ZoneFinder& getFinder() { return (*finder_); }
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ if (!rrset_collection_) {
+ // This is only assigned the first time and remains for the
+ // lifetime of the DatabaseUpdater.
+ rrset_collection_.reset(new RRsetCollection(*this, zone_class_));
+ }
+ return (*rrset_collection_);
+ }
+
virtual void addRRset(const AbstractRRset& rrset);
virtual void deleteRRset(const AbstractRRset& rrset);
virtual void commit();
@@ -1435,6 +1487,7 @@ private:
DiffPhase diff_phase_;
Serial serial_;
boost::scoped_ptr<DatabaseClient::Finder> finder_;
+ boost::shared_ptr<isc::datasrc::RRsetCollection> rrset_collection_;
// This is a set of validation checks commonly used for addRRset() and
// deleteRRset to minimize duplicate code logic and to make the main
@@ -1565,6 +1618,14 @@ isNSEC3KindType(RRType rrtype, const Rdata& rdata) {
void
DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
+ if (rrset_collection_) {
+ isc_throw(InvalidOperation,
+ "Cannot add RRset after an RRsetCollection has been "
+ "requested for ZoneUpdater for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+
validateAddOrDelete("add", rrset, DELETE, ADD);
// It's guaranteed rrset has at least one RDATA at this point.
@@ -1615,6 +1676,14 @@ DatabaseUpdater::addRRset(const AbstractRRset& rrset) {
void
DatabaseUpdater::deleteRRset(const AbstractRRset& rrset) {
+ if (rrset_collection_) {
+ isc_throw(InvalidOperation,
+ "Cannot delete RRset after an RRsetCollection has been "
+ "requested for ZoneUpdater for "
+ << zone_name_ << "/" << zone_class_ << " on "
+ << db_name_);
+ }
+
// If this is the first operation, pretend we are starting a new delete
// sequence after adds. This will simplify the validation below.
if (diff_phase_ == NOT_STARTED) {
@@ -1669,6 +1738,11 @@ DatabaseUpdater::commit() {
accessor_->commit();
committed_ = true; // make sure the destructor won't trigger rollback
+ // Disable the RRsetCollection if it exists.
+ if (rrset_collection_) {
+ rrset_collection_->disableWrapper();
+ }
+
// We release the accessor immediately after commit is completed so that
// we don't hold the possible internal resource any longer.
accessor_.reset();
@@ -1745,7 +1819,7 @@ public:
arg(zone_).arg(rrclass_).arg(accessor_->getDBName());
return (rrset);
} catch (const Exception& ex) {
- LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADR_BADDATA).
+ LOG_ERROR(logger, DATASRC_DATABASE_JOURNALREADER_BADDATA).
arg(zone_).arg(rrclass_).arg(accessor_->getDBName()).
arg(begin_).arg(end_).arg(ex.what());
isc_throw(DataSourceError, "Failed to create RRset from diff on "
diff --git a/src/lib/datasrc/database.h b/src/lib/datasrc/database.h
index 9db8a8f..3302adf 100644
--- a/src/lib/datasrc/database.h
+++ b/src/lib/datasrc/database.h
@@ -155,7 +155,7 @@ public:
///
/// It is empty, but needs a virtual one, since we will use the derived
/// classes in polymorphic way.
- virtual ~DatabaseAccessor() { }
+ virtual ~DatabaseAccessor() {}
/// \brief Retrieve a zone identifier
///
@@ -164,8 +164,8 @@ public:
/// apex), as the DatabaseClient will loop trough the labels itself and
/// find the most suitable zone.
///
- /// It is not specified if and what implementation of this method may throw,
- /// so code should expect anything.
+ /// It is not specified if and what implementation of this method may
+ /// throw, so code should expect anything.
///
/// \param name The (fully qualified) domain name of the zone's apex to be
/// looked up.
@@ -195,6 +195,23 @@ public:
/// or was created by this call).
virtual int addZone(const std::string& name) = 0;
+ /// \brief Delete a zone from the database
+ ///
+ /// Like for deleteRecordToZone, implementations are not required to
+ /// check for the existence of the given zone name, it is the
+ /// responsibility of the caller to do so.
+ ///
+ /// Callers must also start a transaction before calling this method.
+ /// Implementations should throw InvalidOperation if this has not been
+ /// done. Callers should also expect DataSourceError for other potential
+ /// problems specific to the database.
+ ///
+ /// \note This method does not delete other database records related to
+ /// the zone. See \c DataSourceClient::deleteZone for the rationale.
+ ///
+ /// \param zone_id The ID of the zone, that would be returned by getZone().
+ virtual void deleteZone(int zone_id) = 0;
+
/// \brief This holds the internal context of ZoneIterator for databases
///
/// While the ZoneIterator implementation from DatabaseClient does all the
@@ -212,15 +229,15 @@ public:
/// \brief Destructor
///
/// Virtual destructor, so any descendand class is destroyed correctly.
- virtual ~IteratorContext() { }
+ virtual ~IteratorContext() {}
/// \brief Function to provide next resource record
///
/// This function should provide data about the next resource record
/// from the data that is searched. The data is not converted yet.
///
- /// Depending on how the iterator was constructed, there is a difference
- /// in behaviour; for a 'full zone iterator', created with
+ /// Depending on how the iterator was constructed, there is a
+ /// difference in behaviour; for a 'full zone iterator', created with
/// getAllRecords(), all COLUMN_COUNT elements of the array are
/// overwritten.
/// For a 'name iterator', created with getRecords(), the column
@@ -1399,7 +1416,9 @@ public:
/// 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);
+ virtual bool createZone(const isc::dns::Name& zone_name);
+
+ virtual bool deleteZone(const isc::dns::Name& zone_name);
/// \brief Get the zone iterator
///
diff --git a/src/lib/datasrc/datasrc_messages.mes b/src/lib/datasrc/datasrc_messages.mes
index e9b4c90..6ac9db0 100644
--- a/src/lib/datasrc/datasrc_messages.mes
+++ b/src/lib/datasrc/datasrc_messages.mes
@@ -70,6 +70,19 @@ The maximum allowed number of items of the hotspot cache is set to the given
number. If there are too many, some of them will be dropped. The size of 0
means no limit.
+% DATASRC_CHECK_ERROR post-load check of zone %1/%2 failed: %3
+The zone was loaded into the data source successfully, but the content fails
+basic sanity checks. See the message if you want to know what exactly is wrong
+with the data. The data can not be used and previous version, if any, will be
+preserved.
+
+% DATASRC_CHECK_WARNING %1/%2: %3
+The zone was loaded into the data source successfully, but there's some problem
+with the content. The problem does not stop the new version from being used
+(though there may be other problems that do, see DATASRC_CHECK_ERROR),
+but it should still be checked and fixed. See the message to know what exactly
+is wrong with the data.
+
% DATASRC_DATABASE_COVER_NSEC_UNSUPPORTED %1 doesn't support DNSSEC when asked for NSEC data covering %2
The datasource tried to provide an NSEC proof that the named domain does not
exist, but the database backend doesn't support DNSSEC. No proof is included
@@ -144,7 +157,7 @@ instead.
% DATASRC_DATABASE_FOUND_EMPTY_NONTERMINAL empty non-terminal %2 in %1
The domain name does not have any RRs associated with it, so it doesn't
exist in the database. However, it has a subdomain, so it does exist
-in the DNS address space. This type of domain is known an an "empty
+in the DNS address space. This type of domain is known as an "empty
non-terminal" and so we return NXRRSET instead of NXDOMAIN.
% DATASRC_DATABASE_FOUND_NXDOMAIN search in datasource %1 resulted in NXDOMAIN for %2/%3/%4
@@ -202,7 +215,7 @@ a zone's difference sequences from a database-based data source. The
zone's name and class, database name, and the start and end serials
are shown in the message.
-% DATASRC_DATABASE_JOURNALREADR_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
+% DATASRC_DATABASE_JOURNALREADER_BADDATA failed to convert a diff to RRset in %1/%2 on %3 between %4 and %5: %6
This is an error message indicating that a zone's diff is broken and
the data source library failed to convert it to a valid RRset. The
most likely cause of this is that someone has manually modified the
@@ -287,7 +300,7 @@ matching the name. This is returned as the result of the search.
The given wildcard matches the name being sough but it as an empty
nonterminal (e.g. there's nothing at *.example.com but something like
subdomain.*.example.org, do exist: so *.example.org exists in the
-namespace but has no RRs assopciated with it). This will produce NXRRSET.
+namespace but has no RRs associated with it). This will produce NXRRSET.
% DATASRC_DATABASE_WILDCARD_MATCH search in datasource %1 resulted in wildcard match at %2 with RRset %3
The database doesn't contain directly matching name. When searching
@@ -490,7 +503,7 @@ destroyed.
% DATASRC_MEM_WILDCARD_CANCEL wildcard match canceled for '%1'
Debug information. A domain above wildcard was reached, but there's something
below the requested domain. Therefore the wildcard doesn't apply here. This
-behaviour is specified by RFC 1034, section 4.3.3
+behaviour is specified by RFC 1034, section 4.3.3.
% DATASRC_MEM_WILDCARD_DNAME DNAME record in wildcard domain '%1'
The software refuses to load DNAME records into a wildcard domain. It isn't
diff --git a/src/lib/datasrc/rrset_collection_base.cc b/src/lib/datasrc/rrset_collection_base.cc
new file mode 100644
index 0000000..b19f62e
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <datasrc/rrset_collection_base.h>
+#include <datasrc/zone_loader.h>
+#include <exceptions/exceptions.h>
+
+using namespace isc;
+using namespace isc::dns;
+
+namespace isc {
+namespace datasrc {
+
+ConstRRsetPtr
+isc::datasrc::RRsetCollectionBase::find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const
+{
+ if (isDisabled()) {
+ isc_throw(RRsetCollectionError, "This RRsetCollection is disabled.");
+ }
+
+ if (rrclass != rrclass_) {
+ // We could throw an exception here, but RRsetCollection is
+ // expected to support an arbitrary collection of RRsets, and it
+ // can be queried just as arbitrarily. So we just return nothing
+ // here.
+ return (ConstRRsetPtr());
+ }
+
+ ZoneFinder& finder = updater_.getFinder();
+ try {
+ ZoneFinderContextPtr result =
+ finder.find(name, rrtype,
+ ZoneFinder::NO_WILDCARD | ZoneFinder::FIND_GLUE_OK);
+ // We return the result rrset only if the result code is
+ // SUCCESS. We return empty if CNAME, DNAME, DELEGATION,
+ // etc. are returned by the ZoneFinder.
+ //
+ // Note that in the case that the queried type itself is CNAME
+ // or DNAME, then the finder will return SUCCESS.
+ if (result->code == ZoneFinder::SUCCESS) {
+ return (result->rrset);
+ } else {
+ return (ConstRRsetPtr());
+ }
+ } catch (const OutOfZone&) {
+ // As RRsetCollection is an arbitrary set of RRsets, in case the
+ // searched name is out of zone, we return nothing instead of
+ // propagating the exception.
+ return (ConstRRsetPtr());
+ } catch (const DataSourceError& e) {
+ isc_throw(RRsetCollectionError,
+ "ZoneFinder threw a DataSourceError: "
+ << e.getMessage().c_str());
+ }
+}
+
+} // end of namespace datasrc
+} // end of namespace isc
diff --git a/src/lib/datasrc/rrset_collection_base.h b/src/lib/datasrc/rrset_collection_base.h
new file mode 100644
index 0000000..c02df9a
--- /dev/null
+++ b/src/lib/datasrc/rrset_collection_base.h
@@ -0,0 +1,126 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_DATASRC_H
+#define RRSET_COLLECTION_DATASRC_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+#include <datasrc/zone.h>
+
+namespace isc {
+namespace datasrc {
+
+/// \brief A forward declaration
+class ZoneUpdater;
+
+/// \brief datasrc derivation of \c isc::dns::RRsetCollectionBase.
+///
+/// This is an abstract class that adds datasrc related detail to
+/// \c isc::dns::RRsetCollectionBase. Derived classes need to complete
+/// the implementation (add iterator support, etc.) before using it.
+class RRsetCollectionBase : public isc::dns::RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// No reference (count via \c shared_ptr) to the \c ZoneUpdater is
+ /// acquired. The RRsetCollection must not be used after its
+ /// \c ZoneUpdater has been destroyed.
+ ///
+ /// \param updater The ZoneUpdater to wrap around.
+ /// \param rrclass The RRClass of the records in the zone.
+ RRsetCollectionBase(ZoneUpdater& updater,
+ const isc::dns::RRClass& rrclass) :
+ updater_(updater),
+ rrclass_(rrclass),
+ disabled_(false)
+ {}
+
+ /// \brief Destructor
+ virtual ~RRsetCollectionBase() {}
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// Note that not all records added through the updater may
+ /// necessarily be found by this method, such as RRs subject to
+ /// DNAME substitution.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if \c find() results in
+ /// some underlying datasrc error, or if \c disable() was called.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \returns The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+protected:
+ /// \brief Disable the RRsetCollection.
+ ///
+ /// After calling this method, calling operations such as find() or
+ /// using the iterator would result in an \c
+ /// isc::dns::RRsetCollectionError. This method is typically called
+ /// in the \c commit() implementations of some \c ZoneUpdaters.
+ void disable() {
+ disabled_ = true;
+ }
+
+ /// \brief Return if the RRsetCollection is disabled.
+ bool isDisabled() const {
+ return (disabled_);
+ }
+
+ /// \brief See \c isc::dns::RRsetCollectionBase::getBeginning() for
+ /// documentation.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error, or if \c disable() was
+ /// called.
+ virtual IterPtr getBeginning() = 0;
+
+ /// \brief See \c isc::dns::RRsetCollectionBase::getEnd() for
+ /// documentation.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error, or if \c disable() was
+ /// called.
+ virtual IterPtr getEnd() = 0;
+
+private:
+ ZoneUpdater& updater_;
+ isc::dns::RRClass rrclass_;
+ bool disabled_;
+};
+
+/// \brief A pointer-like type pointing to an
+/// \c isc::datasrc::RRsetCollectionBase object.
+///
+/// This type is used to handle RRsetCollections in a polymorphic manner
+/// in libdatasrc.
+typedef boost::shared_ptr<isc::datasrc::RRsetCollectionBase> RRsetCollectionPtr;
+
+} // end of namespace datasrc
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_DATASRC_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/datasrc/sqlite3_accessor.cc b/src/lib/datasrc/sqlite3_accessor.cc
index 4ef49d0..bd71544 100644
--- a/src/lib/datasrc/sqlite3_accessor.cc
+++ b/src/lib/datasrc/sqlite3_accessor.cc
@@ -79,7 +79,8 @@ enum StatementID {
DEL_ZONE_NSEC3_RECORDS = 20,
DEL_NSEC3_RECORD = 21,
ADD_ZONE = 22,
- NUM_STATEMENTS = 23
+ DELETE_ZONE = 23,
+ NUM_STATEMENTS = 24
};
const char* const text_statements[NUM_STATEMENTS] = {
@@ -165,7 +166,9 @@ const char* const text_statements[NUM_STATEMENTS] = {
"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
+ "INSERT INTO zones (name, rdclass) VALUES (?1, ?2)", // ADD_ZONE
+ // DELETE_ZONE: delete a zone from the zones table
+ "DELETE FROM zones WHERE id=?1" // DELETE_ZONE
};
struct SQLite3Parameters {
@@ -643,6 +646,19 @@ SQLite3Accessor::addZone(const std::string& name) {
return (getzone_result.second);
}
+void
+SQLite3Accessor::deleteZone(int zone_id) {
+ // Transaction should have been started by the caller
+ if (!dbparameters_->in_transaction) {
+ isc_throw(InvalidOperation, "performing deleteZone on SQLite3 "
+ "data source without transaction");
+ }
+
+ StatementProcessor proc(*dbparameters_, DELETE_ZONE, "delete zone");
+ proc.bindInt(1, zone_id);
+ proc.exec();
+}
+
namespace {
// Conversion to plain char
diff --git a/src/lib/datasrc/sqlite3_accessor.h b/src/lib/datasrc/sqlite3_accessor.h
index c5773d0..d014193 100644
--- a/src/lib/datasrc/sqlite3_accessor.h
+++ b/src/lib/datasrc/sqlite3_accessor.h
@@ -139,6 +139,10 @@ public:
/// \return the id of the zone that has been added
virtual int addZone(const std::string& name);
+ // Nothing special to add for this implementation (the base class
+ // description is sufficient).
+ virtual void deleteZone(int zone_id);
+
/// \brief Look up all resource records for a name
///
/// This implements the getRecords() method from DatabaseAccessor
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 61858bd..7c61826 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -118,3 +118,5 @@ EXTRA_DIST += testdata/new_minor_schema.sqlite3
EXTRA_DIST += testdata/newschema.sqlite3
EXTRA_DIST += testdata/oldschema.sqlite3
EXTRA_DIST += testdata/static.zone
+EXTRA_DIST += testdata/novalidate.zone
+EXTRA_DIST += testdata/checkwarn.zone
diff --git a/src/lib/datasrc/tests/database_unittest.cc b/src/lib/datasrc/tests/database_unittest.cc
index 174da04..9f5ade4 100644
--- a/src/lib/datasrc/tests/database_unittest.cc
+++ b/src/lib/datasrc/tests/database_unittest.cc
@@ -55,6 +55,7 @@ namespace {
// Imaginary zone IDs used in the mock accessor below.
const int READONLY_ZONE_ID = 42;
+const int NEW_ZONE_ID = 420;
const int WRITABLE_ZONE_ID = 4200;
// Commonly used test data
@@ -257,26 +258,43 @@ const char* TEST_NSEC3_RECORDS[][5] = {
*/
class NopAccessor : public DatabaseAccessor {
public:
- NopAccessor() : database_name_("mock_database")
- { }
+ NopAccessor() : database_name_("mock_database") {
+ zones_["example.org."] = READONLY_ZONE_ID;
+ zones_["null.example.org."] = 13;
+ zones_["empty.example.org."] = 0;
+ zones_["bad.example.org."] = -1;
+ }
virtual std::pair<bool, int> getZone(const std::string& name) const {
- if (name == "example.org.") {
- return (std::pair<bool, int>(true, READONLY_ZONE_ID));
- } else if (name == "null.example.org.") {
- return (std::pair<bool, int>(true, 13));
- } else if (name == "empty.example.org.") {
- return (std::pair<bool, int>(true, 0));
- } else if (name == "bad.example.org.") {
- return (std::pair<bool, int>(true, -1));
+ std::map<std::string, int>::const_iterator found = zones_.find(name);
+ if (found != zones_.end()) {
+ return (std::pair<bool, int>(true, found->second));
} else {
return (std::pair<bool, int>(false, 0));
}
}
- virtual int addZone(const std::string&) {
- isc_throw(isc::NotImplemented,
- "This database datasource can't add zones");
+ // A simple implementation of addZone.
+ virtual int addZone(const std::string& zone_name) {
+ if (zone_name == "example.com.") {
+ zones_[zone_name] = NEW_ZONE_ID;
+ }
+
+ // for simplicity we assume zone_name is in zones_ at this point
+ return (zones_[zone_name]);
+ }
+
+ // A simple implementation of deleteZone.
+ virtual void deleteZone(int zone_id) {
+ std::map<std::string, int>::iterator it = zones_.begin();
+ std::map<std::string, int>::iterator end = zones_.end();
+ while (it != end) {
+ if (it->second == zone_id) {
+ zones_.erase(it);
+ return;
+ }
+ ++it;
+ }
}
virtual boost::shared_ptr<DatabaseAccessor> clone() {
@@ -337,7 +355,7 @@ public:
private:
const std::string database_name_;
-
+ std::map<std::string, int> zones_;
};
/**
@@ -438,7 +456,7 @@ public:
// Check any attempt of multiple transactions
if (did_transaction_) {
- isc_throw(isc::Unexpected, "MockAccessor::startTransaction() "
+ isc_throw(DataSourceError, "MockAccessor::startTransaction() "
"called multiple times - likely a bug in the test");
}
@@ -447,6 +465,14 @@ public:
did_transaction_ = true;
}
+ // If the test needs multiple calls to startTransaction() and knows it's
+ // safe, it can use this method to disable the safeguard check in
+ // startTransaction(); the test can also use this method by emulating a
+ // lock conflict by setting is_allowed to false.
+ void allowMoreTransaction(bool is_allowed) {
+ did_transaction_ = !is_allowed;
+ }
+
private:
class DomainIterator : public IteratorContext {
public:
@@ -1293,6 +1319,17 @@ public:
}
}
+ // Mock-only; control whether to allow subsequent transaction.
+ void allowMoreTransaction(bool is_allowed) {
+ if (is_mock_) {
+ // Use a separate variable for MockAccessor&; some compilers
+ // would be confused otherwise.
+ MockAccessor& mock_accessor =
+ dynamic_cast<MockAccessor&>(*current_accessor_);
+ mock_accessor.allowMoreTransaction(is_allowed);
+ }
+ }
+
// Some tests only work for MockAccessor. We remember whether our accessor
// is of that type.
bool is_mock_;
@@ -2184,6 +2221,12 @@ TYPED_TEST(DatabaseClientTest, findDelegation) {
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
isc::dns::Name("dname.example.org."));
+ // below.dname.example.org. has an A record
+ doFindTest(*finder, isc::dns::Name("below.dname.example.org."),
+ isc::dns::RRType::A(), isc::dns::RRType::DNAME(),
+ this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
+ this->expected_sig_rdatas_, ZoneFinder::RESULT_DEFAULT,
+ isc::dns::Name("dname.example.org."));
doFindTest(*finder, isc::dns::Name("really.deep.below.dname.example.org."),
isc::dns::RRType::AAAA(), isc::dns::RRType::DNAME(),
this->rrttl_, ZoneFinder::DNAME, this->expected_rdatas_,
@@ -4104,51 +4147,289 @@ TYPED_TEST(DatabaseClientTest, createZone) {
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));
- }
+ // Adding a new zone; it should work and return true
+ ASSERT_TRUE(this->client_->createZone(new_name));
+ const DataSourceClient::FindResult
+ zone2(this->client_->findZone(new_name));
+ ASSERT_EQ(result::SUCCESS, zone2.code);
+ // And the second call should return false since
+ // it already exists
+ this->allowMoreTransaction(true);
+ ASSERT_FALSE(this->client_->createZone(new_name));
}
TYPED_TEST(DatabaseClientTest, createZoneRollbackOnLocked) {
- // 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);
+ this->allowMoreTransaction(false);
ASSERT_THROW(this->client_->createZone(new_name), DataSourceError);
// createZone started a transaction as well, but since it failed,
// it should have been rolled back. Roll back the other one as
// well, and the next attempt should succeed
updater.reset();
+ this->allowMoreTransaction(true);
ASSERT_TRUE(this->client_->createZone(new_name));
}
TYPED_TEST(DatabaseClientTest, createZoneRollbackOnExists) {
- // 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
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
ASSERT_TRUE(this->client_->createZone(new_name));
}
+TYPED_TEST(DatabaseClientTest, deleteZone) {
+ // Check the zone currently exists.
+ EXPECT_EQ(result::SUCCESS, this->client_->findZone(this->zname_).code);
+
+ // Deleting an existing zone; it should work and return true (previously
+ // existed and is now deleted)
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+
+ // Now it's not found by findZone
+ EXPECT_EQ(result::NOTFOUND, this->client_->findZone(this->zname_).code);
+
+ // And the second call should return false since it doesn't exist any more
+ this->allowMoreTransaction(true);
+ EXPECT_FALSE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnLocked) {
+ isc::datasrc::ZoneUpdaterPtr updater =
+ this->client_->getUpdater(this->zname_, true);
+
+ // updater locks the DB so deleteZone() will fail.
+ this->allowMoreTransaction(false);
+ EXPECT_THROW(this->client_->deleteZone(this->zname_), DataSourceError);
+
+ // deleteZone started a transaction as well, but since it failed,
+ // it should have been rolled back. Roll back the other one as
+ // well, and the next attempt should succeed
+ updater.reset();
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST(DatabaseClientTest, deleteZoneRollbackOnNotFind) {
+ // attempt of deleting non-existent zone. result in false
+ const Name new_name("example.com");
+ EXPECT_FALSE(this->client_->deleteZone(new_name));
+
+ // deleteZone started a transaction, but since the zone didn't even exist
+ // the transaction was not committed but should have been rolled back.
+ // The first transaction shouldn't leave any state, lock, etc, that
+ // would hinder the second attempt.
+ this->allowMoreTransaction(true);
+ EXPECT_TRUE(this->client_->deleteZone(this->zname_));
+}
+
+TYPED_TEST_CASE(RRsetCollectionTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+ RRsetCollectionTest() :
+ DatabaseClientTest<ACCESSOR_TYPE>(),
+ updater(this->client_->getUpdater(this->zname_, false)),
+ collection(updater->getRRsetCollection())
+ {}
+
+ ZoneUpdaterPtr updater;
+ isc::datasrc::RRsetCollectionBase& collection;
+};
+
+TYPED_TEST(RRsetCollectionTest, find) {
+ // Test the find() that returns ConstRRsetPtr
+ ConstRRsetPtr rrset = this->collection.find(Name("www.example.org."),
+ RRClass::IN(), RRType::A());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = this->collection.find(Name("foo.example.org"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+ RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = this->collection.find(Name("www.example.org"), this->qclass_,
+ RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = this->collection.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+
+ // Out-of-zone find()s must not throw.
+ rrset = this->collection.find(Name("www.example.com"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // "cname.example.org." with type CNAME should return the CNAME RRset
+ rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+ RRType::CNAME());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::CNAME(), rrset->getType());
+ EXPECT_EQ(Name("cname.example.org"), rrset->getName());
+
+ // "cname.example.org." with type A should return nothing
+ rrset = this->collection.find(Name("cname.example.org"), this->qclass_,
+ RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // "dname.example.org." with type DNAME should return the DNAME RRset
+ rrset = this->collection.find(Name("dname.example.org"), this->qclass_,
+ RRType::DNAME());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::DNAME(), rrset->getType());
+ EXPECT_EQ(Name("dname.example.org"), rrset->getName());
+
+ // "below.dname.example.org." with type AAAA should return nothing
+ rrset = this->collection.find(Name("below.dname.example.org"),
+ this->qclass_, RRType::AAAA());
+ EXPECT_FALSE(rrset);
+
+ // "below.dname.example.org." with type A does not return the record
+ // (see top of file). See \c isc::datasrc::RRsetCollectionBase::find()
+ // documentation for details.
+ rrset = this->collection.find(Name("below.dname.example.org"),
+ this->qclass_, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // With the FIND_GLUE_OK option passed to ZoneFinder's find(),
+ // searching for "delegation.example.org." with type NS should
+ // return the NS record. Without FIND_GLUE_OK, ZoneFinder's find()
+ // would return DELEGATION and the find() below would return
+ // nothing.
+ rrset = this->collection.find(Name("delegation.example.org"),
+ this->qclass_, RRType::NS());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::NS(), rrset->getType());
+ EXPECT_EQ(Name("delegation.example.org"), rrset->getName());
+
+ // With the NO_WILDCARD option passed to ZoneFinder's find(),
+ // searching for some "foo.wildcard.example.org." would make
+ // ZoneFinder's find() return NXDOMAIN, and the find() below should
+ // return nothing.
+ rrset = this->collection.find(Name("foo.wild.example.org"),
+ this->qclass_, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // Searching directly for "*.wild.example.org." should return the
+ // record.
+ rrset = this->collection.find(Name("*.wild.example.org"),
+ this->qclass_, RRType::A());
+ ASSERT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(Name("*.wild.example.org"), rrset->getName());
+}
+
+TYPED_TEST(RRsetCollectionTest, iteratorTest) {
+ // Iterators are currently not implemented.
+ EXPECT_THROW(this->collection.begin(), isc::NotImplemented);
+ EXPECT_THROW(this->collection.end(), isc::NotImplemented);
+}
+
+typedef RRsetCollectionTest<MockAccessor> MockRRsetCollectionTest;
+
+TEST_F(MockRRsetCollectionTest, findError) {
+ // A test using the MockAccessor for checking that FindError is
+ // thrown properly if a find attempt using ZoneFinder results in a
+ // DataSourceError.
+ //
+ // The "dsexception.example.org." name is rigged by the MockAccessor
+ // to throw a DataSourceError.
+ EXPECT_THROW({
+ this->collection.find(Name("dsexception.example.org"), this->qclass_,
+ RRType::A());
+ }, RRsetCollectionError);
+}
+
+TYPED_TEST_CASE(RRsetCollectionAndUpdaterTest, TestAccessorTypes);
+
+// This test fixture is templated so that we can share (most of) the test
+// cases with different types of data sources. Note that in test cases
+// we need to use 'this' to refer to member variables of the test class.
+template <typename ACCESSOR_TYPE>
+class RRsetCollectionAndUpdaterTest : public DatabaseClientTest<ACCESSOR_TYPE> {
+public:
+ RRsetCollectionAndUpdaterTest() :
+ DatabaseClientTest<ACCESSOR_TYPE>(),
+ updater_(this->client_->getUpdater(this->zname_, false))
+ {}
+
+ ZoneUpdaterPtr updater_;
+};
+
+// Test that using addRRset() or deleteRRset() on the ZoneUpdater throws
+// after an RRsetCollection is created.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, updateThrows) {
+ // 1. Addition test
+
+ // addRRset() must not throw.
+ this->updater_->addRRset(*this->rrset_);
+
+ // Now setup a new updater and call getRRsetCollection() on it.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ (void) this->updater_->getRRsetCollection();
+
+ // addRRset() must throw isc::InvalidOperation here.
+ EXPECT_THROW(this->updater_->addRRset(*this->rrset_),
+ isc::InvalidOperation);
+
+ // 2. Deletion test
+
+ // deleteRRset() must not throw.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+ this->updater_->deleteRRset(*this->rrset_);
+
+ // Now setup a new updater and call getRRsetCollection() on it.
+ this->updater_.reset();
+ this->updater_ = this->client_->getUpdater(this->zname_, false);
+ this->updater_->addRRset(*this->rrset_);
+ (void) this->updater_->getRRsetCollection();
+
+ // deleteRRset() must throw isc::InvalidOperation here.
+ EXPECT_THROW(this->updater_->deleteRRset(*this->rrset_),
+ isc::InvalidOperation);
+}
+
+// Test that using an RRsetCollection after calling commit() on the
+// ZoneUpdater throws, as the RRsetCollection is disabled.
+TYPED_TEST(RRsetCollectionAndUpdaterTest, useAfterCommitThrows) {
+ isc::datasrc::RRsetCollectionBase& collection =
+ this->updater_->getRRsetCollection();
+
+ // find() must not throw here.
+ collection.find(Name("foo.wild.example.org"), this->qclass_, RRType::A());
+
+ this->updater_->commit();
+
+ // find() must throw RRsetCollectionError here, as the
+ // RRsetCollection is disabled.
+ EXPECT_THROW(collection.find(Name("foo.wild.example.org"),
+ this->qclass_, RRType::A()),
+ RRsetCollectionError);
+}
+
}
diff --git a/src/lib/datasrc/tests/master_loader_callbacks_test.cc b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
index 19ec4d2..dc44461 100644
--- a/src/lib/datasrc/tests/master_loader_callbacks_test.cc
+++ b/src/lib/datasrc/tests/master_loader_callbacks_test.cc
@@ -65,6 +65,9 @@ public:
virtual ZoneFinder& getFinder() {
isc_throw(isc::NotImplemented, "Not to be called in this test");
}
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ isc_throw(isc::NotImplemented, "Not to be called in this test");
+ }
virtual void deleteRRset(const isc::dns::AbstractRRset&) {
isc_throw(isc::NotImplemented, "Not to be called in this test");
}
diff --git a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
index 4f82f7b..a45c2bd 100644
--- a/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
+++ b/src/lib/datasrc/tests/memory/rdata_serialization_unittest.cc
@@ -71,20 +71,20 @@ struct TestRdata {
// unusual and corner cases).
const TestRdata test_rdata_list[] = {
{"IN", "A", "192.0.2.1", 0},
- {"IN", "NS", "ns.example.com", 0},
- {"IN", "CNAME", "cname.example.com", 0},
- {"IN", "SOA", "ns.example.com root.example.com 0 0 0 0 0", 0},
- {"IN", "PTR", "reverse.example.com", 0},
+ {"IN", "NS", "ns.example.com.", 0},
+ {"IN", "CNAME", "cname.example.com.", 0},
+ {"IN", "SOA", "ns.example.com. root.example.com. 0 0 0 0 0", 0},
+ {"IN", "PTR", "reverse.example.com.", 0},
{"IN", "HINFO", "\"cpu-info\" \"OS-info\"", 1},
- {"IN", "MINFO", "root.example.com mbox.example.com", 0},
- {"IN", "MX", "10 mx.example.com", 0},
+ {"IN", "MINFO", "root.example.com. mbox.example.com.", 0},
+ {"IN", "MX", "10 mx.example.com.", 0},
{"IN", "TXT", "\"test1\" \"test 2\"", 1},
- {"IN", "RP", "root.example.com. rp-text.example.com", 0},
- {"IN", "AFSDB", "1 afsdb.example.com", 0},
+ {"IN", "RP", "root.example.com. rp-text.example.com.", 0},
+ {"IN", "AFSDB", "1 afsdb.example.com.", 0},
{"IN", "AAAA", "2001:db8::1", 0},
- {"IN", "SRV", "1 0 10 target.example.com", 0},
- {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com", 1},
- {"IN", "DNAME", "dname.example.com", 0},
+ {"IN", "SRV", "1 0 10 target.example.com.", 0},
+ {"IN", "NAPTR", "100 50 \"s\" \"http\" \"\" _http._tcp.example.com.", 1},
+ {"IN", "DNAME", "dname.example.com.", 0},
{"IN", "DS", "12892 5 2 5F0EB5C777586DE18DA6B5", 1},
{"IN", "SSHFP", "1 1 dd465c09cfa51fb45020cc83316fff", 1},
// We handle RRSIG separately, so it's excluded from the list
@@ -98,7 +98,7 @@ const TestRdata test_rdata_list[] = {
{"IN", "TYPE65000", "\\# 3 010203", 1}, // some "custom" type
{"IN", "TYPE65535", "\\# 0", 1}, // max RR type, 0-length RDATA
{"CH", "A", "\\# 2 0102", 1}, // A RR for non-IN class; varlen data
- {"CH", "NS", "ns.example.com", 0}, // class CH, generic data
+ {"CH", "NS", "ns.example.com.", 0}, // class CH, generic data
{"CH", "TXT", "BIND10", 1}, // ditto
{"HS", "A", "\\# 5 0102030405", 1}, // A RR for non-IN class; varlen data
{NULL, NULL, NULL, 0}
diff --git a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
index 8dff6d5..c263304 100644
--- a/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
+++ b/src/lib/datasrc/tests/sqlite3_accessor_unittest.cc
@@ -20,6 +20,8 @@
#include <dns/rrclass.h>
+#include <exceptions/exceptions.h>
+
#include <sqlite3.h>
#include <gtest/gtest.h>
@@ -1615,4 +1617,50 @@ TEST_F(SQLite3Update, addZoneWhileLocked) {
EXPECT_FALSE(accessor->getZone(new_zone).first);
}
+//
+// Tests for deleteZone() follow.
+//
+TEST_F(SQLite3Update, deleteZone) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Calling deleteZone without transaction should fail
+ EXPECT_THROW(accessor->deleteZone(zone_info.first), isc::InvalidOperation);
+
+ // Delete the zone. Then confirm it, both before and after commit.
+ accessor->startTransaction();
+ accessor->deleteZone(zone_info.second);
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+ accessor->commit();
+ EXPECT_FALSE(accessor->getZone("example.com.").first);
+
+ // Records are not deleted.
+ std::string data[DatabaseAccessor::COLUMN_COUNT];
+ EXPECT_TRUE(accessor->getRecords("example.com.", zone_id, false)
+ ->getNext(data));
+}
+
+TEST_F(SQLite3Update, deleteZoneWhileLocked) {
+ const std::pair<bool, int> zone_info(accessor->getZone("example.com."));
+ ASSERT_TRUE(zone_info.first);
+ zone_id = zone_info.second;
+
+ // Adding another (not commit yet), it should lock the db
+ const std::string new_zone = "new.example.com.";
+ accessor->startTransaction();
+ zone_id = accessor->addZone(new_zone);
+
+ // deleteZone should throw an exception that it is locked
+ another_accessor->startTransaction();
+ EXPECT_THROW(another_accessor->deleteZone(zone_id), DataSourceError);
+ // Commit should do nothing, but not fail
+ another_accessor->commit();
+
+ accessor->rollback();
+
+ // The zone should still exist.
+ EXPECT_TRUE(accessor->getZone("example.com.").first);
+}
+
} // end anonymous namespace
diff --git a/src/lib/datasrc/tests/testdata/checkwarn.zone b/src/lib/datasrc/tests/testdata/checkwarn.zone
new file mode 100644
index 0000000..e985085
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/checkwarn.zone
@@ -0,0 +1,4 @@
+example.org. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+example.org. 86400 IN NS ns.example.org.
+; Missing the address for the nameserver. This should generate a warning, but not error.
+www.example.org. 3600 IN A 192.0.2.1
diff --git a/src/lib/datasrc/tests/testdata/novalidate.zone b/src/lib/datasrc/tests/testdata/novalidate.zone
new file mode 100644
index 0000000..4bbf12a
--- /dev/null
+++ b/src/lib/datasrc/tests/testdata/novalidate.zone
@@ -0,0 +1,3 @@
+example.org. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2010030802 1800 900 604800 86400
+; Missing the NS here, will generate an error in post-load check of the zone.
+www.example.org. 3600 IN A 192.0.2.1
diff --git a/src/lib/datasrc/tests/zone_finder_context_unittest.cc b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
index 6844712..85b167e 100644
--- a/src/lib/datasrc/tests/zone_finder_context_unittest.cc
+++ b/src/lib/datasrc/tests/zone_finder_context_unittest.cc
@@ -81,7 +81,7 @@ addRRset(ZoneUpdaterPtr updater, ConstRRsetPtr rrset) {
}
DataSourceClientPtr
-createSQLite3Client(RRClass zclass, const Name& zname) {
+createSQLite3Client(RRClass zclass, const Name& zname, stringstream& ss) {
// We always begin with an empty template SQLite3 DB file and install
// the zone data from the zone file to ensure both cases have the
// same test data.
@@ -93,7 +93,6 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
// Note that neither updater nor SQLite3 accessor checks this condition,
// so this should succeed.
ZoneUpdaterPtr updater = client->getUpdater(zname, false);
- stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
masterLoad(ss, Name::ROOT_NAME(), zclass,
boost::bind(addRRset, updater, _1));
updater->commit();
@@ -101,6 +100,12 @@ createSQLite3Client(RRClass zclass, const Name& zname) {
return (client);
}
+DataSourceClientPtr
+createSQLite3ClientWithNS(RRClass zclass, const Name& zname) {
+ stringstream ss("ns.example.com. 3600 IN A 192.0.2.7");
+ return (createSQLite3Client(zclass, zname, ss));
+}
+
// The test class. Its parameterized so we can share the test scnearios
// for any concrete data source implementaitons.
class ZoneFinderContextTest :
@@ -134,7 +139,7 @@ protected:
// We test the in-memory and SQLite3 data source implementations.
INSTANTIATE_TEST_CASE_P(, ZoneFinderContextTest,
::testing::Values(createInMemoryClient,
- createSQLite3Client));
+ createSQLite3ClientWithNS));
TEST_P(ZoneFinderContextTest, getAdditionalAuthNS) {
ZoneFinderContextPtr ctx = finder_->find(qzone_, RRType::NS());
@@ -430,4 +435,25 @@ TEST_P(ZoneFinderContextTest, getAdditionalWithRRSIGOnly) {
result_sets_.begin(), result_sets_.end());
}
+TEST(ZoneFinderContextSQLite3Test, escapedText) {
+ // This test checks that TXTLike data, when written to a database,
+ // is escaped correctly before stored in the database. The actual
+ // escaping is done in the toText() method of TXTLike objects, but
+ // we check anyway if this also carries over to the ZoneUpdater.
+ RRClass zclass(RRClass::IN());
+ Name zname("example.org");
+ stringstream ss("escaped.example.org. 3600 IN TXT Hello~World\\;\\\"");
+ DataSourceClientPtr client = createSQLite3Client(zclass, zname, ss);
+ ZoneFinderPtr finder = client->findZone(zname).zone_finder;
+
+ // If there is no escaping, the following will throw an exception
+ // when it tries to construct a TXT RRset using the data from the
+ // database.
+ ZoneFinderContextPtr ctx = finder->find(Name("escaped.example.org"),
+ RRType::TXT());
+ EXPECT_EQ(ZoneFinder::SUCCESS, ctx->code);
+ EXPECT_EQ("escaped.example.org. 3600 IN TXT \"Hello~World\\;\\\"\"\n",
+ ctx->rrset->toText());
+}
+
}
diff --git a/src/lib/datasrc/tests/zone_loader_unittest.cc b/src/lib/datasrc/tests/zone_loader_unittest.cc
index b19a843..28153e4 100644
--- a/src/lib/datasrc/tests/zone_loader_unittest.cc
+++ b/src/lib/datasrc/tests/zone_loader_unittest.cc
@@ -27,6 +27,8 @@
#include <gtest/gtest.h>
#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/foreach.hpp>
#include <string>
#include <vector>
@@ -34,6 +36,7 @@ using isc::dns::RRClass;
using isc::dns::Name;
using isc::dns::RRType;
using isc::dns::ConstRRsetPtr;
+using isc::dns::RRsetPtr;
using std::string;
using std::vector;
using boost::shared_ptr;
@@ -64,10 +67,9 @@ public:
// 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_;
+ vector<RRsetPtr> rrsets_;
+ // List of rrsets as texts, for easier manipulation
+ vector<string> rrset_texts_;
bool commit_called_;
// If set to true, getUpdater returns NULL
bool missing_zone_;
@@ -75,6 +77,26 @@ public:
RRClass rrclass_;
};
+// Test implementation of RRsetCollectionBase.
+class TestRRsetCollection : public isc::datasrc::RRsetCollectionBase {
+public:
+ TestRRsetCollection(ZoneUpdater& updater,
+ const isc::dns::RRClass& rrclass) :
+ isc::datasrc::RRsetCollectionBase(updater, rrclass)
+ {}
+
+ virtual ~TestRRsetCollection() {}
+
+protected:
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ isc_throw(isc::NotImplemented, "This method is not implemented.");
+ }
+};
+
// The updater isn't really correct according to the API. For example,
// the whole client can be committed only once in its lifetime. The
// updaters would influence each other if there were more. But we
@@ -82,18 +104,36 @@ public:
// and this way, it is much simpler.
class Updater : public ZoneUpdater {
public:
- Updater(MockClient* client) :
+ Updater(MockClient* client, const Name& name) :
client_(client),
- finder_(client_->rrclass_)
+ finder_(client_->rrclass_, name, client_->rrsets_)
{}
virtual ZoneFinder& getFinder() {
return (finder_);
}
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() {
+ if (!rrset_collection_) {
+ rrset_collection_.reset(new TestRRsetCollection(*this,
+ client_->rrclass_));
+ }
+ return (*rrset_collection_);
+ }
virtual void addRRset(const isc::dns::AbstractRRset& rrset) {
if (client_->commit_called_) {
isc_throw(DataSourceError, "Add after commit");
}
- client_->rrsets_.push_back(rrset.toText());
+ // We need to copy the RRset. We don't do it properly (we omit the
+ // signature, for example), because we don't need to.
+ RRsetPtr new_rrset(new isc::dns::BasicRRset(rrset.getName(),
+ rrset.getClass(),
+ rrset.getType(),
+ rrset.getTTL()));
+ for (isc::dns::RdataIteratorPtr i(rrset.getRdataIterator());
+ !i->isLast(); i->next()) {
+ new_rrset->addRdata(i->getCurrent());
+ }
+ client_->rrsets_.push_back(new_rrset);
+ client_->rrset_texts_.push_back(rrset.toText());
}
virtual void deleteRRset(const isc::dns::AbstractRRset&) {
isc_throw(isc::NotImplemented, "Method not used in tests");
@@ -103,21 +143,37 @@ public:
}
private:
MockClient* client_;
+ boost::scoped_ptr<TestRRsetCollection> rrset_collection_;
class Finder : public ZoneFinder {
public:
- Finder(const RRClass& rrclass) :
- class_(rrclass)
+ Finder(const RRClass& rrclass, const Name& name,
+ const vector<RRsetPtr>& rrsets) :
+ class_(rrclass),
+ name_(name),
+ rrsets_(rrsets)
{}
virtual RRClass getClass() const {
return (class_);
}
virtual Name getOrigin() const {
- isc_throw(isc::NotImplemented, "Method not used in tests");
+ return (name_);
}
- virtual shared_ptr<Context> find(const Name&, const RRType&,
- const FindOptions)
+ virtual shared_ptr<Context> find(const Name& name, const RRType& type,
+ const FindOptions options)
{
- isc_throw(isc::NotImplemented, "Method not used in tests");
+ // The method is not completely correct. It ignores many special
+ // cases and also the options except for the result. But this is
+ // enough for the tests. We care only about exact match here.
+ BOOST_FOREACH(const RRsetPtr& rrset, rrsets_) {
+ if (rrset->getName() == name && rrset->getType() == type) {
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(SUCCESS, rrset))));
+ }
+ }
+ return (shared_ptr<Context>(
+ new GenericContext(*this, options,
+ ResultContext(NXRRSET, ConstRRsetPtr()))));
}
virtual shared_ptr<Context> findAll(const Name&,
vector<ConstRRsetPtr>&,
@@ -130,6 +186,8 @@ private:
}
private:
const RRClass class_;
+ const Name name_;
+ const vector<RRsetPtr>& rrsets_;
} finder_;
};
@@ -144,7 +202,7 @@ MockClient::getUpdater(const Name& name, bool replace, bool journaling) const {
// 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))));
+ return (ZoneUpdaterPtr(new Updater(const_cast<MockClient*>(this), name)));
}
class ZoneLoaderTest : public ::testing::Test {
@@ -157,10 +215,12 @@ protected:
{}
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.
+ // Currently, source_client_ is of InMemoryClient and its load()
+ // uses a different code than the ZoneLoader (so we can cross-check
+ // the implementations). Currently, the load() doesn't perform any
+ // post-load checks. It will change in #2499, at which point the
+ // loading may start failing depending on details of the test data. We
+ // should prepare the data by some different method then.
source_client_.load(zone, string(TEST_DATA_DIR) + "/" + filename);
}
private:
@@ -186,6 +246,11 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// It gets the updater directly in the constructor
ASSERT_EQ(1, destination_client_.provided_updaters_.size());
EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
+
+ // Counter is initialized to 0, progress is "unknown" in case of copy.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// Now load the whole zone
loader.load();
EXPECT_TRUE(destination_client_.commit_called_);
@@ -194,13 +259,19 @@ TEST_F(ZoneLoaderTest, copyUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // Check various counters. getRRCount should be identical of the RRs
+ // we've seen. Progress is still "unknown" in the copy operation.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// Ensure known order.
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
- destination_client_.rrsets_.front());
+ destination_client_.rrset_texts_.front());
EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
- destination_client_.rrsets_.back());
+ destination_client_.rrset_texts_.back());
// It isn't possible to try again now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -221,6 +292,11 @@ TEST_F(ZoneLoaderTest, copyUnsignedIncremental) {
// Not committed yet, we didn't complete the loading
EXPECT_FALSE(destination_client_.commit_called_);
+ // Check we can get intermediate counters. Progress is always "unknown"
+ // in case of copy.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(ZoneLoader::PROGRESS_UNKNOWN, loader.getProgress());
+
// This is unusual, but allowed. Check it doesn't do anything
loader.loadIncremental(0);
EXPECT_EQ(10, destination_client_.rrsets_.size());
@@ -249,18 +325,18 @@ TEST_F(ZoneLoaderTest, copySigned) {
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());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
// Due to the R at the beginning, this one should be last
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
"1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
- destination_client_.rrsets_[0]);
+ destination_client_.rrset_texts_[0]);
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
"NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
" EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
"KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
"/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
- destination_client_.rrsets_[1]);
+ destination_client_.rrset_texts_[1]);
}
// If the destination zone does not exist, it throws
@@ -289,6 +365,11 @@ TEST_F(ZoneLoaderTest, classMismatch) {
TEST_F(ZoneLoaderTest, loadUnsigned) {
ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
TEST_DATA_DIR "/root.zone");
+
+ // Counter and progress are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
// It gets the updater directly in the constructor
ASSERT_EQ(1, destination_client_.provided_updaters_.size());
EXPECT_EQ(Name::ROOT_NAME(), destination_client_.provided_updaters_[0]);
@@ -300,13 +381,19 @@ TEST_F(ZoneLoaderTest, loadUnsigned) {
// The count is 34 because we expect the RRs to be separated.
EXPECT_EQ(34, destination_client_.rrsets_.size());
+
+ // getRRCount should be identical of the RRs we've seen. progress
+ // should reach 100% (= 1).
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
// Ensure known order.
- std::sort(destination_client_.rrsets_.begin(),
- destination_client_.rrsets_.end());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
EXPECT_EQ(". 518400 IN NS a.root-servers.net.\n",
- destination_client_.rrsets_.front());
+ destination_client_.rrset_texts_.front());
EXPECT_EQ("m.root-servers.net. 3600000 IN AAAA 2001:dc3::35\n",
- destination_client_.rrsets_.back());
+ destination_client_.rrset_texts_.back());
// It isn't possible to try again now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
@@ -320,6 +407,10 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
ZoneLoader loader(destination_client_, Name::ROOT_NAME(),
TEST_DATA_DIR "/root.zone");
+ // Counters are initialized to 0.
+ EXPECT_EQ(0, loader.getRRCount());
+ EXPECT_EQ(0, loader.getProgress());
+
// Try loading few RRs first.
loader.loadIncremental(10);
// We should get the 10 we asked for
@@ -330,11 +421,26 @@ TEST_F(ZoneLoaderTest, loadUnsignedIncremental) {
EXPECT_EQ(10, destination_client_.rrsets_.size());
EXPECT_FALSE(destination_client_.commit_called_);
+ // Check we can get intermediate counters. Expected progress is calculated
+ // based on the size of the zone file and the offset to the end of 10th RR
+ // (subject to future changes to the file, but we assume it's a rare
+ // event.). The expected value should be the exact expression that
+ // getProgress() should do internally, so EXPECT_EQ() should work here,
+ // but floating-point comparison can be always tricky we use
+ // EXPECT_DOUBLE_EQ just in case.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ // file size = 1541, offset = 428 (27.77%).
+ EXPECT_DOUBLE_EQ(static_cast<double>(428) / 1541, loader.getProgress());
+
// We can finish the rest
loader.loadIncremental(30);
EXPECT_EQ(34, destination_client_.rrsets_.size());
EXPECT_TRUE(destination_client_.commit_called_);
+ // Counters are updated accordingly. Progress should reach 100%.
+ EXPECT_EQ(destination_client_.rrsets_.size(), loader.getRRCount());
+ EXPECT_EQ(1, loader.getProgress());
+
// No more loading now
EXPECT_THROW(loader.load(), isc::InvalidOperation);
EXPECT_THROW(loader.loadIncremental(1), isc::InvalidOperation);
@@ -359,18 +465,18 @@ TEST_F(ZoneLoaderTest, loadSigned) {
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());
+ std::sort(destination_client_.rrset_texts_.begin(),
+ destination_client_.rrset_texts_.end());
// Due to the R at the beginning, this one should be last
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN NSEC3 "
"1 0 10 AABBCCDD RKOF8QMFRB5F2V9EJHFBVB2JPVSA0DJD A RRSIG\n",
- destination_client_.rrsets_[0]);
+ destination_client_.rrset_texts_[0]);
EXPECT_EQ("09GM5T42SMIMT7R8DF6RTG80SFMS1NLU.example.org. 1200 IN RRSIG "
"NSEC3 7 3 1200 20120301040838 20120131040838 19562 example.org."
" EdwMeepLf//lV+KpCAN+213Scv1rrZyj4i2OwoCP4XxxS3CWGSuvYuKOyfZc8w"
"KRcrD/4YG6nZVXE0s5O8NahjBJmDIyVt4WkfZ6QthxGg8ggLVvcD3dFksPyiKHf"
"/WrTOZPSsxvN5m/i1Ey6+YWS01Gf3WDCMWDauC7Nmh3CTM=\n",
- destination_client_.rrsets_[1]);
+ destination_client_.rrset_texts_[1]);
}
// Test it throws when there's no such file
@@ -392,4 +498,47 @@ TEST_F(ZoneLoaderTest, loadSyntaxError) {
EXPECT_FALSE(destination_client_.commit_called_);
}
+// Test there's validation of the data in the zone loader.
+TEST_F(ZoneLoaderTest, loadCheck) {
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ TEST_DATA_DIR "/novalidate.zone");
+ EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+ // The messages go to the log. We don't have an easy way to examine them.
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// The same test, but for copying from other data source
+TEST_F(ZoneLoaderTest, copyCheck) {
+ prepareSource(Name("example.org"), "novalidate.zone");
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ source_client_);
+
+ EXPECT_THROW(loader.loadIncremental(10), ZoneContentError);
+ // The messages go to the log. We don't have an easy way to examine them.
+ EXPECT_FALSE(destination_client_.commit_called_);
+}
+
+// Check a warning doesn't disrupt the loading of the zone
+TEST_F(ZoneLoaderTest, loadCheckWarn) {
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ TEST_DATA_DIR "/checkwarn.zone");
+ EXPECT_TRUE(loader.loadIncremental(10));
+ // The messages go to the log. We don't have an easy way to examine them.
+ // But the zone was committed and contains all 3 RRs
+ EXPECT_TRUE(destination_client_.commit_called_);
+ EXPECT_EQ(3, destination_client_.rrsets_.size());
+}
+
+TEST_F(ZoneLoaderTest, copyCheckWarn) {
+ prepareSource(Name("example.org"), "checkwarn.zone");
+ ZoneLoader loader(destination_client_, Name("example.org"),
+ source_client_);
+ EXPECT_TRUE(loader.loadIncremental(10));
+ // The messages go to the log. We don't have an easy way to examine them.
+ // But the zone was committed and contains all 3 RRs
+ EXPECT_TRUE(destination_client_.commit_called_);
+ EXPECT_EQ(3, destination_client_.rrsets_.size());
+
+}
+
}
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index 0d7438d..01d6a83 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -21,6 +21,7 @@
#include <datasrc/exceptions.h>
#include <datasrc/result.h>
+#include <datasrc/rrset_collection_base.h>
#include <utility>
#include <vector>
@@ -740,6 +741,9 @@ typedef boost::shared_ptr<ZoneFinder::Context> ZoneFinderContextPtr;
/// \c ZoneFinder::Context object.
typedef boost::shared_ptr<ZoneFinder::Context> ConstZoneFinderContextPtr;
+/// \brief A forward declaration
+class RRsetCollectionBase;
+
/// The base class to make updates to a single zone.
///
/// On construction, each derived class object will start a "transaction"
@@ -802,6 +806,29 @@ public:
/// \return A reference to a \c ZoneFinder for the updated zone
virtual ZoneFinder& getFinder() = 0;
+ /// Return an RRsetCollection for the updater.
+ ///
+ /// This method returns an \c RRsetCollection for the updater,
+ /// implementing the \c isc::datasrc::RRsetCollectionBase
+ /// interface. Typically, the returned \c RRsetCollection is a
+ /// singleton for its \c ZoneUpdater. The returned RRsetCollection
+ /// object must not be used after its corresponding \c ZoneUpdater
+ /// has been destroyed. The returned RRsetCollection object may be
+ /// used to search RRsets from the ZoneUpdater. The actual
+ /// \c RRsetCollection returned has a behavior dependent on the
+ /// \c ZoneUpdater implementation.
+ ///
+ /// The behavior of the RRsetCollection is similar to the behavior
+ /// of the \c Zonefinder returned by \c getFinder().
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called.
+ /// Implementations of \c ZoneUpdater may disable a previously
+ /// returned \c RRsetCollection after \c commit() is called. If an
+ /// \c RRsetCollection is disabled, using methods such as \c find()
+ /// and using its iterator would cause an exception to be
+ /// thrown. See \c isc::datasrc::RRsetCollectionBase for details.
+ virtual isc::datasrc::RRsetCollectionBase& getRRsetCollection() = 0;
+
/// Add an RRset to a zone via the updater
///
/// This may be revisited in a future version, but right now the intended
@@ -849,6 +876,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called. In this
+ /// case, implementations throw an \c InvalidOperation exception.
+ ///
/// If journaling was requested when getting this updater, it will reject
/// to add the RRset if the squence doesn't look like and IXFR (see
/// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
@@ -920,6 +951,10 @@ public:
/// calls after \c commit() the implementation must throw a
/// \c DataSourceError exception.
///
+ /// Implementations of \c ZoneUpdater may not allow adding or
+ /// deleting RRsets after \c getRRsetCollection() is called. In this
+ /// case, implementations throw an \c InvalidOperation exception.
+ ///
/// If journaling was requested when getting this updater, it will reject
/// to add the RRset if the squence doesn't look like and IXFR (see
/// DataSourceClient::getUpdater). In such case isc::BadValue is thrown.
diff --git a/src/lib/datasrc/zone_loader.cc b/src/lib/datasrc/zone_loader.cc
index 01f216e..9e9dd4a 100644
--- a/src/lib/datasrc/zone_loader.cc
+++ b/src/lib/datasrc/zone_loader.cc
@@ -19,23 +19,35 @@
#include <datasrc/data_source.h>
#include <datasrc/iterator.h>
#include <datasrc/zone.h>
+#include <datasrc/logger.h>
+#include <datasrc/rrset_collection_base.h>
#include <dns/rrset.h>
+#include <dns/zone_checker.h>
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <boost/bind.hpp>
+
+#include <string>
using isc::dns::Name;
using isc::dns::ConstRRsetPtr;
using isc::dns::MasterLoader;
+using isc::dns::MasterLexer;
namespace isc {
namespace datasrc {
+const double ZoneLoader::PROGRESS_UNKNOWN = -1;
+
ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
DataSourceClient& source) :
// Separate the RRsets as that is possibly faster (the data source doesn't
// have to aggregate them) and also because our limit semantics.
iterator_(source.getIterator(zone_name, true)),
updater_(destination.getUpdater(zone_name, true, false)),
- complete_(false)
+ complete_(false), rr_count_(0)
{
// The getIterator should never return NULL. So we check it.
// Or should we throw instead?
@@ -56,11 +68,25 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
}
}
+namespace {
+// Unified callback to install RR and increment RR count at the same time.
+void
+addRR(ZoneUpdater* updater, size_t* rr_count,
+ const dns::Name& name, const dns::RRClass& rrclass,
+ const dns::RRType& type, const dns::RRTTL& ttl,
+ const dns::rdata::RdataPtr& data)
+{
+ isc::dns::BasicRRset rrset(name, rrclass, type, ttl);
+ rrset.addRdata(data);
+ updater->addRRset(rrset);
+ ++*rr_count;
+}
+}
+
ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
const char* filename) :
updater_(destination.getUpdater(zone_name, true, false)),
- complete_(false),
- loaded_ok_(true)
+ complete_(false), loaded_ok_(true), rr_count_(0)
{
if (updater_ == ZoneUpdaterPtr()) {
isc_throw(DataSourceError, "Zone " << zone_name << " not found in "
@@ -74,7 +100,9 @@ ZoneLoader::ZoneLoader(DataSourceClient& destination, const Name& zone_name,
createMasterLoaderCallbacks(zone_name,
updater_->getFinder().getClass(),
&loaded_ok_),
- createMasterLoaderAddCallback(*updater_)));
+ boost::bind(addRR,
+ updater_.get(), &rr_count_,
+ _1, _2, _3, _4, _5)));
}
}
@@ -83,7 +111,7 @@ namespace {
// Copy up to limit RRsets from source to destination
bool
copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
- size_t limit)
+ size_t limit, size_t& rr_count_)
{
size_t loaded = 0;
while (loaded < limit) {
@@ -95,10 +123,27 @@ copyRRsets(const ZoneUpdaterPtr& destination, const ZoneIteratorPtr& source,
destination->addRRset(*rrset);
}
++loaded;
+ rr_count_ += rrset->getRdataCount();
}
return (false); // Not yet, there may be more
}
+void
+logWarning(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_WARN(logger, DATASRC_CHECK_WARNING).arg(*zone_name).arg(*rrclass).
+ arg(reason);
+}
+
+void
+logError(const dns::Name* zone_name, const dns::RRClass* rrclass,
+ const std::string& reason)
+{
+ LOG_ERROR(logger, DATASRC_CHECK_ERROR).arg(*zone_name).arg(*rrclass).
+ arg(reason);
+}
+
} // end unnamed namespace
bool
@@ -108,8 +153,8 @@ ZoneLoader::loadIncremental(size_t limit) {
"Loading has been completed previously");
}
- if (iterator_ == ZoneIteratorPtr()) {
- assert(loader_.get() != NULL);
+ if (!iterator_) {
+ assert(loader_);
try {
complete_ = loader_->loadIncremental(limit);
} catch (const isc::dns::MasterLoaderError& e) {
@@ -119,14 +164,58 @@ ZoneLoader::loadIncremental(size_t limit) {
isc_throw(MasterFileError, "Error while loading master file");
}
} else {
- complete_ = copyRRsets(updater_, iterator_, limit);
+ complete_ = copyRRsets(updater_, iterator_, limit, rr_count_);
}
if (complete_) {
+ // Everything is loaded. Perform some basic sanity checks on the zone.
+ RRsetCollectionBase& collection = updater_->getRRsetCollection();
+ const dns::Name& zone_name(updater_->getFinder().getOrigin());
+ const dns::RRClass& zone_class(updater_->getFinder().getClass());
+ const dns::ZoneCheckerCallbacks
+ callbacks(boost::bind(&logError, &zone_name, &zone_class, _1),
+ boost::bind(&logWarning, &zone_name, &zone_class, _1));
+ if (!dns::checkZone(zone_name, zone_class, collection, callbacks)) {
+ // The post-load check failed.
+ loaded_ok_ = false;
+ isc_throw(ZoneContentError, "Errors found when validating zone " <<
+ zone_name << "/" << zone_class);
+ }
updater_->commit();
}
return (complete_);
}
+size_t
+ZoneLoader::getRRCount() const {
+ return (rr_count_);
+}
+
+double
+ZoneLoader::getProgress() const {
+ if (!loader_) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ const size_t pos = loader_->getPosition();
+ const size_t total_size = loader_->getSize();
+
+ // If the current position is 0, progress should definitely be 0; we
+ // don't bother to check the total size even if it's "unknown".
+ if (pos == 0) {
+ return (0);
+ }
+
+ // These cases shouldn't happen with our usage of MasterLoader. So, in
+ // theory, we could throw here; however, since this method is expected
+ // to be used for informational purposes only, that's probably too harsh.
+ // So we return "unknown" instead.
+ if (total_size == MasterLexer::SOURCE_SIZE_UNKNOWN || total_size == 0) {
+ return (PROGRESS_UNKNOWN);
+ }
+
+ return (static_cast<double>(pos) / total_size);
+}
+
} // end namespace datasrc
} // end namespace isc
diff --git a/src/lib/datasrc/zone_loader.h b/src/lib/datasrc/zone_loader.h
index 2946116..2a4559e 100644
--- a/src/lib/datasrc/zone_loader.h
+++ b/src/lib/datasrc/zone_loader.h
@@ -48,6 +48,17 @@ public:
{}
};
+/// \brief Exception thrown when the zone doesn't pass post-load check.
+///
+/// This is thrown by the ZoneLoader when the zone is loaded, but it
+/// doesn't pass basic sanity checks.
+class ZoneContentError : public DataSourceError {
+public:
+ ZoneContentError(const char* file, size_t line, const char* what) :
+ DataSourceError(file, line, what)
+ {}
+};
+
/// \brief Class to load data into a data source client.
///
/// This is a small wrapper class that is able to load data into a data source.
@@ -107,6 +118,7 @@ public:
/// \throw DataSourceError in case some error (possibly low-level) happens.
/// \throw MasterFileError when the master_file is badly formatted or some
/// similar problem is found when loading the master file.
+ /// \throw ZoneContentError when the zone doesn't pass sanity check.
void load() {
while (!loadIncremental(1000)) { // 1000 is arbitrary largish number
// Body intentionally left blank.
@@ -123,6 +135,12 @@ public:
/// pauses in the loading for some purposes (for example reporting
/// progress).
///
+ /// After the last RR is loaded, a sanity check of the zone is performed by
+ /// isc::dns::validateZone. It reports errors and warnings by logging them
+ /// directly. If there are any errors, a ZoneContentError exception is
+ /// thrown and the load is aborted (preserving the old version of zone, if
+ /// any).
+ ///
/// \param limit The maximum allowed number of RRs to be loaded during this
/// call.
/// \return True in case the loading is completed, false if there's more
@@ -133,12 +151,69 @@ public:
/// \throw DataSourceError in case some error (possibly low-level) happens.
/// \throw MasterFileError when the master_file is badly formatted or some
/// similar problem is found when loading the master file.
+ /// \throw ZoneContentError when the zone doesn't pass sanity check.
/// \note If the limit is exactly the number of RRs available to be loaded,
/// the method still returns false and true'll be returned on the next
- /// call (which will load 0 RRs). This is because the end of iterator or
- /// master file is detected when reading past the end, not when the last
- /// one is read.
+ /// call (which will load 0 RRs). This is because the end of iterator
+ /// or master file is detected when reading past the end, not when the
+ /// last one is read.
bool loadIncremental(size_t limit);
+
+ /// \brief Return the number of RRs loaded.
+ ///
+ /// This method returns the number of RRs loaded via this loader by the
+ /// time of the call. Before starting the load it will return 0.
+ /// It will return the total number of RRs of the zone on and after
+ /// completing the load.
+ ///
+ /// \throw None
+ size_t getRRCount() const;
+
+ /// \brief Return the current progress of the loader.
+ ///
+ /// This method returns the current estimated progress of loader as a
+ /// value between 0 and 1 (inclusive); it's 0 before starting the load,
+ /// and 1 at the completion, and a value between these (exclusive) in the
+ /// middle of loading. It's an implementation detail how to calculate
+ /// the progress, which may vary depending on how the loader is
+ /// constructed and may even be impossible to detect effectively.
+ ///
+ /// If the progress cannot be determined, this method returns a special
+ /// value of PROGRESS_UNKNOWN, which is not included in the range between
+ /// 0 and 1.
+ ///
+ /// As such, the application should use the return value only for
+ /// informational purposes such as logging. For example, it shouldn't
+ /// be used to determine whether loading is completed by comparing it
+ /// to 1. It should also expect the possibility of getting
+ /// \c PROGRESS_UNKNOWN at any call to this method; it shouldn't assume
+ /// the specific way of internal implementation as described below (which
+ /// is provided for informational purposes only).
+ ///
+ /// In this implementation, if the loader is constructed with a file
+ /// name, the progress value is measured by the number of characters
+ /// read from the zone file divided by the size of the zone file
+ /// (with taking into account any included files). Note that due to
+ /// the possibility of intermediate included files, the total file size
+ /// cannot be fully fixed until the completion of the load. And, due to
+ /// this possibility, return values from this method may not always
+ /// increase monotonically.
+ ///
+ /// If it's constructed with another data source client, this method
+ /// always returns \c PROGRESS_UNKNOWN; in future, however, it may become
+ /// possible to return something more useful, e.g, based on the result
+ /// of \c getRRCount() and the total number of RRs if the underlying data
+ /// source can provide the latter value efficiently.
+ ///
+ /// \throw None
+ double getProgress() const;
+
+ /// \brief A special value for \c getProgress, meaning the progress is
+ /// unknown.
+ ///
+ /// See the method description for details.
+ static const double PROGRESS_UNKNOWN;
+
private:
/// \brief The iterator used as source of data in case of the copy mode.
const ZoneIteratorPtr iterator_;
@@ -150,9 +225,14 @@ private:
bool complete_;
/// \brief Was the loading successful?
bool loaded_ok_;
+ size_t rr_count_;
};
}
}
-#endif
+#endif // DATASRC_ZONE_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dhcp/Makefile.am b/src/lib/dhcp/Makefile.am
index 571f11d..822c4e3 100644
--- a/src/lib/dhcp/Makefile.am
+++ b/src/lib/dhcp/Makefile.am
@@ -14,23 +14,24 @@ CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libb10-dhcp++.la
libb10_dhcp___la_SOURCES =
+libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
libb10_dhcp___la_SOURCES += duid.cc duid.h
+libb10_dhcp___la_SOURCES += hwaddr.cc hwaddr.h
libb10_dhcp___la_SOURCES += iface_mgr.cc iface_mgr.h
libb10_dhcp___la_SOURCES += iface_mgr_bsd.cc
libb10_dhcp___la_SOURCES += iface_mgr_linux.cc
libb10_dhcp___la_SOURCES += iface_mgr_sun.cc
libb10_dhcp___la_SOURCES += libdhcp++.cc libdhcp++.h
-libb10_dhcp___la_SOURCES += option.cc option.h
-libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
-libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
-libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option6_ia.cc option6_ia.h
libb10_dhcp___la_SOURCES += option6_iaaddr.cc option6_iaaddr.h
libb10_dhcp___la_SOURCES += option6_addrlst.cc option6_addrlst.h
-libb10_dhcp___la_SOURCES += option4_addrlst.cc option4_addrlst.h
libb10_dhcp___la_SOURCES += option_int.h
libb10_dhcp___la_SOURCES += option_int_array.h
-libb10_dhcp___la_SOURCES += dhcp6.h dhcp4.h
+libb10_dhcp___la_SOURCES += option.cc option.h
+libb10_dhcp___la_SOURCES += option_custom.cc option_custom.h
+libb10_dhcp___la_SOURCES += option_data_types.cc option_data_types.h
+libb10_dhcp___la_SOURCES += option_definition.cc option_definition.h
libb10_dhcp___la_SOURCES += pkt6.cc pkt6.h
libb10_dhcp___la_SOURCES += pkt4.cc pkt4.h
libb10_dhcp___la_SOURCES += std_option_defs.h
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 60b9706..688885b 100644
--- a/src/lib/dhcp/duid.h
+++ b/src/lib/dhcp/duid.h
@@ -86,7 +86,7 @@ typedef boost::shared_ptr<DUID> DuidPtr;
///
/// This class is intended to be a generic IPv4 client identifier. It can hold
/// a client-id
-class ClientId : DUID {
+class ClientId : public DUID {
public:
/// @brief Maximum size of a client ID
///
@@ -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;
diff --git a/src/lib/dhcp/hwaddr.cc b/src/lib/dhcp/hwaddr.cc
new file mode 100644
index 0000000..d19f2ad
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.cc
@@ -0,0 +1,62 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <iomanip>
+#include <sstream>
+#include <vector>
+
+namespace isc {
+namespace dhcp {
+
+HWAddr::HWAddr()
+ :htype_(HTYPE_ETHER) {
+}
+
+HWAddr::HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype)
+ :hwaddr_(hwaddr, hwaddr + len), htype_(htype) {
+}
+
+HWAddr::HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype)
+ :hwaddr_(hwaddr), htype_(htype) {
+}
+
+std::string HWAddr::toText() const {
+ std::stringstream tmp;
+ tmp << "hwtype=" << static_cast<int>(htype_) << " ";
+ tmp << std::hex;
+ bool delim = false;
+ for (std::vector<uint8_t>::const_iterator it = hwaddr_.begin();
+ it != hwaddr_.end(); ++it) {
+ if (delim) {
+ tmp << ":";
+ }
+ tmp << std::setw(2) << std::setfill('0') << static_cast<unsigned int>(*it);
+ delim = true;
+ }
+ return (tmp.str());
+}
+
+bool HWAddr::operator==(const HWAddr& other) const {
+ return ((this->htype_ == other.htype_) &&
+ (this->hwaddr_ == other.hwaddr_));
+}
+
+bool HWAddr::operator!=(const HWAddr& other) const {
+ return !(*this == other);
+}
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
diff --git a/src/lib/dhcp/hwaddr.h b/src/lib/dhcp/hwaddr.h
new file mode 100644
index 0000000..93b06a1
--- /dev/null
+++ b/src/lib/dhcp/hwaddr.h
@@ -0,0 +1,67 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef HWADDR_H
+#define HWADDR_H
+
+#include <vector>
+#include <stdint.h>
+#include <stddef.h>
+#include <dhcp/dhcp4.h>
+#include <boost/shared_ptr.hpp>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Hardware type that represents information from DHCPv4 packet
+struct HWAddr {
+public:
+
+ /// @brief default constructor
+ HWAddr();
+
+ /// @brief constructor, based on C-style pointer and length
+ /// @param hwaddr pointer to hardware address
+ /// @param len length of the address pointed by hwaddr
+ /// @param htype hardware type
+ HWAddr(const uint8_t* hwaddr, size_t len, uint8_t htype);
+
+ /// @brief constructor, based on C++ vector<uint8_t>
+ /// @param hwaddr const reference to hardware address
+ /// @param htype hardware type
+ HWAddr(const std::vector<uint8_t>& hwaddr, uint8_t htype);
+
+ // Vector that keeps the actual hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ // Hardware type
+ uint8_t htype_;
+
+ /// @brief Returns textual representation of a client-id (e.g. 00:01:02:03)
+ std::string toText() const;
+
+ /// @brief Compares two hardware addresses for equality
+ bool operator==(const HWAddr& other) const;
+
+ /// @brief Compares two hardware addresses for inequality
+ bool operator!=(const HWAddr& other) const;
+};
+
+/// @brief Shared pointer to a hardware address structure
+typedef boost::shared_ptr<HWAddr> HWAddrPtr;
+
+}; // end of isc::dhcp namespace
+}; // end of isc namespace
+
+#endif // HWADDR_H
diff --git a/src/lib/dhcp/libdhcp++.cc b/src/lib/dhcp/libdhcp++.cc
index b19ed21..a3921cc 100644
--- a/src/lib/dhcp/libdhcp++.cc
+++ b/src/lib/dhcp/libdhcp++.cc
@@ -74,6 +74,34 @@ LibDHCP::getOptionDef(const Option::Universe u, const uint16_t code) {
return (OptionDefinitionPtr());
}
+bool
+LibDHCP::isStandardOption(const Option::Universe u, const uint16_t code) {
+ if (u == Option::V6) {
+ if (code < 79 &&
+ code != 10 &&
+ code != 35) {
+ return (true);
+ }
+
+ } else if (u == Option::V4) {
+ if (!(code == 84 ||
+ code == 96 ||
+ (code > 101 && code < 112) ||
+ code == 115 ||
+ code == 126 ||
+ code == 127 ||
+ (code > 146 && code < 150) ||
+ (code > 177 && code < 208) ||
+ (code > 213 && code < 220) ||
+ (code > 221 && code < 224))) {
+ return (true);
+ }
+
+ }
+
+ return (false);
+}
+
OptionPtr
LibDHCP::optionFactory(Option::Universe u,
uint16_t type,
diff --git a/src/lib/dhcp/libdhcp++.h b/src/lib/dhcp/libdhcp++.h
index c325aa5..bc47405 100644
--- a/src/lib/dhcp/libdhcp++.h
+++ b/src/lib/dhcp/libdhcp++.h
@@ -55,6 +55,21 @@ public:
static OptionDefinitionPtr getOptionDef(const Option::Universe u,
const uint16_t code);
+ /// @brief Check if the specified option is a standard option.
+ ///
+ /// @param u universe (V4 or V6)
+ /// @param code option code.
+ ///
+ /// @return true if the specified option is a standard option.
+ /// @todo We arleady create option definitions for the subset if
+ /// standard options. We are aiming that this function checks
+ /// the presence of the standard option definition and if it finds
+ /// it, then the true value is returned. However, at this point
+ /// this is not doable because some of the definitions (for less
+ /// important options) are not created yet.
+ static bool isStandardOption(const Option::Universe u,
+ const uint16_t code);
+
/// @brief Factory function to create instance of option.
///
/// Factory method creates instance of specified option. The option
diff --git a/src/lib/dhcp/option_definition.cc b/src/lib/dhcp/option_definition.cc
index e1d7cb6..d2b5aae 100644
--- a/src/lib/dhcp/option_definition.cc
+++ b/src/lib/dhcp/option_definition.cc
@@ -22,6 +22,9 @@
#include <dhcp/option_int.h>
#include <dhcp/option_int_array.h>
#include <util/encode/hex.h>
+#include <util/strutil.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
using namespace std;
using namespace isc::util;
@@ -176,10 +179,10 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
if (values.empty()) {
isc_throw(InvalidOptionValue, "no option value specified");
}
- writeToBuffer(values[0], type_, buf);
+ writeToBuffer(util::str::trim(values[0]), type_, buf);
} else if (array_type_ && type_ != OPT_RECORD_TYPE) {
for (size_t i = 0; i < values.size(); ++i) {
- writeToBuffer(values[i], type_, buf);
+ writeToBuffer(util::str::trim(values[i]), type_, buf);
}
} else if (type_ == OPT_RECORD_TYPE) {
const RecordFieldsCollection& records = getRecordFields();
@@ -189,7 +192,8 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
<< " provided.");
}
for (size_t i = 0; i < records.size(); ++i) {
- writeToBuffer(values[i], records[i], buf);
+ writeToBuffer(util::str::trim(values[i]),
+ records[i], buf);
}
}
return (optionFactory(u, type, buf.begin(), buf.end()));
@@ -205,16 +209,29 @@ OptionDefinition::sanityCheckUniverse(const Option::Universe expected_universe,
void
OptionDefinition::validate() const {
+
+ using namespace boost::algorithm;
+
std::ostringstream err_str;
- if (name_.empty()) {
- // Option name must not be empty.
- err_str << "option name must not be empty.";
- } else if (name_.find(" ") != string::npos) {
- // Option name must not contain spaces.
- err_str << "option name must not contain spaces.";
+
+ // Allowed characters in the option name are: lower or
+ // upper case letters, digits, underscores and hyphens.
+ // Empty option spaces are not allowed.
+ if (!all(name_, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) ||
+ name_.empty() ||
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option name.
+ all(find_head(name_, 1), boost::is_any_of(std::string("-_"))) ||
+ all(find_tail(name_, 1), boost::is_any_of(std::string("-_")))) {
+ err_str << "invalid option name '" << name_ << "'";
+
} else if (type_ >= OPT_UNKNOWN_TYPE) {
// Option definition must be of a known type.
err_str << "option type value " << type_ << " is out of range.";
+
} else if (array_type_) {
if (type_ == OPT_STRING_TYPE) {
// Array of strings is not allowed because there is no way
@@ -223,9 +240,12 @@ OptionDefinition::validate() const {
err_str << "array of strings is not a valid option definition.";
} else if (type_ == OPT_BINARY_TYPE) {
err_str << "array of binary values is not a valid option definition.";
+
} else if (type_ == OPT_EMPTY_TYPE) {
err_str << "array of empty value is not a valid option definition.";
+
}
+
} else if (type_ == OPT_RECORD_TYPE) {
// At least two data fields should be added to the record. Otherwise
// non-record option definition could be used.
@@ -233,6 +253,7 @@ OptionDefinition::validate() const {
err_str << "invalid number of data fields: " << getRecordFields().size()
<< " specified for the option of type 'record'. Expected at"
<< " least 2 fields.";
+
} else {
// If the number of fields is valid we have to check if their order
// is valid too. We check that string or binary data fields are not
diff --git a/src/lib/dhcp/option_definition.h b/src/lib/dhcp/option_definition.h
index 9f6bef2..efcaba0 100644
--- a/src/lib/dhcp/option_definition.h
+++ b/src/lib/dhcp/option_definition.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -42,6 +42,14 @@ public:
isc::Exception(file, line, what) { };
};
+/// @brief Exception to be thrown when the particular option definition
+/// duplicates existing option definition.
+class DuplicateOptionDefinition : public Exception {
+public:
+ DuplicateOptionDefinition(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
/// @brief Forward declaration to OptionDefinition.
class OptionDefinition;
@@ -361,7 +369,7 @@ public:
/// @brief Factory function to create option with array of integer values.
///
- /// @param universe (V4 or V6).
+ /// @param u universe (V4 or V6).
/// @param type option type.
/// @param begin iterator pointing to the beginning of the buffer.
/// @param end iterator pointing to the end of the buffer.
@@ -492,6 +500,9 @@ typedef boost::multi_index_container<
>
> OptionDefContainer;
+/// Pointer to an option definition container.
+typedef boost::shared_ptr<OptionDefContainer> OptionDefContainerPtr;
+
/// Type of the index #1 - option type.
typedef OptionDefContainer::nth_index<1>::type OptionDefContainerTypeIndex;
/// Pair of iterators to represent the range of options definitions
diff --git a/src/lib/dhcp/pkt4.cc b/src/lib/dhcp/pkt4.cc
index 15c8f4e..d3b22de 100644
--- a/src/lib/dhcp/pkt4.cc
+++ b/src/lib/dhcp/pkt4.cc
@@ -40,8 +40,7 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(DHCPTypeToBootpType(msg_type)),
- htype_(HTYPE_ETHER),
- hlen_(0),
+ hwaddr_(new HWAddr()),
hops_(0),
transid_(transid),
secs_(0),
@@ -50,12 +49,12 @@ Pkt4::Pkt4(uint8_t msg_type, uint32_t transid)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(DHCPV4_PKT_HDR_LEN),
- msg_type_(msg_type)
+ bufferOut_(DHCPV4_PKT_HDR_LEN)
{
- memset(chaddr_, 0, MAX_CHADDR_LEN);
memset(sname_, 0, MAX_SNAME_LEN);
memset(file_, 0, MAX_FILE_LEN);
+
+ setType(msg_type);
}
Pkt4::Pkt4(const uint8_t* data, size_t len)
@@ -66,6 +65,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
local_port_(DHCP4_SERVER_PORT),
remote_port_(DHCP4_CLIENT_PORT),
op_(BOOTREQUEST),
+ hwaddr_(new HWAddr()),
transid_(0),
secs_(0),
flags_(0),
@@ -73,8 +73,7 @@ Pkt4::Pkt4(const uint8_t* data, size_t len)
yiaddr_(DEFAULT_ADDRESS),
siaddr_(DEFAULT_ADDRESS),
giaddr_(DEFAULT_ADDRESS),
- bufferOut_(0), // not used, this is RX packet
- msg_type_(DHCPDISCOVER)
+ bufferOut_(0) // not used, this is RX packet
{
if (len < DHCPV4_PKT_HDR_LEN) {
isc_throw(OutOfRange, "Truncated DHCPv4 packet (len=" << len
@@ -105,9 +104,15 @@ Pkt4::len() {
bool
Pkt4::pack() {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't build Pkt4 packet. HWAddr not set.");
+ }
+
+ size_t hw_len = hwaddr_->hwaddr_.size();
+
bufferOut_.writeUint8(op_);
- bufferOut_.writeUint8(htype_);
- bufferOut_.writeUint8(hlen_);
+ bufferOut_.writeUint8(hwaddr_->htype_);
+ bufferOut_.writeUint8(hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN);
bufferOut_.writeUint8(hops_);
bufferOut_.writeUint32(transid_);
bufferOut_.writeUint16(secs_);
@@ -116,7 +121,23 @@ Pkt4::pack() {
bufferOut_.writeUint32(yiaddr_);
bufferOut_.writeUint32(siaddr_);
bufferOut_.writeUint32(giaddr_);
- bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
+
+ if (hw_len <= MAX_CHADDR_LEN) {
+ // write up to 16 bytes of the hardware address (CHADDR field is 16
+ // bytes long in DHCPv4 message).
+ bufferOut_.writeData(&hwaddr_->hwaddr_[0],
+ (hw_len < MAX_CHADDR_LEN ? hw_len : MAX_CHADDR_LEN) );
+ hw_len = MAX_CHADDR_LEN - hw_len;
+ } else {
+ hw_len = MAX_CHADDR_LEN;
+ }
+
+ // write (len) bytes of padding
+ vector<uint8_t> zeros(hw_len, 0);
+ bufferOut_.writeData(&zeros[0], hw_len);
+ // bufferOut_.writeData(chaddr_, MAX_CHADDR_LEN);
+
bufferOut_.writeData(sname_, MAX_SNAME_LEN);
bufferOut_.writeData(file_, MAX_FILE_LEN);
@@ -145,8 +166,8 @@ Pkt4::unpack() {
}
op_ = bufferIn.readUint8();
- htype_ = bufferIn.readUint8();
- hlen_ = bufferIn.readUint8();
+ uint8_t htype = bufferIn.readUint8();
+ uint8_t hlen = bufferIn.readUint8();
hops_ = bufferIn.readUint8();
transid_ = bufferIn.readUint32();
secs_ = bufferIn.readUint16();
@@ -155,10 +176,16 @@ Pkt4::unpack() {
yiaddr_ = IOAddress(bufferIn.readUint32());
siaddr_ = IOAddress(bufferIn.readUint32());
giaddr_ = IOAddress(bufferIn.readUint32());
- bufferIn.readData(chaddr_, MAX_CHADDR_LEN);
+
+ vector<uint8_t> hw_addr(MAX_CHADDR_LEN, 0);
+ bufferIn.readVector(hw_addr, MAX_CHADDR_LEN);
bufferIn.readData(sname_, MAX_SNAME_LEN);
bufferIn.readData(file_, MAX_FILE_LEN);
+ hw_addr.resize(hlen);
+
+ hwaddr_ = HWAddrPtr(new HWAddr(hw_addr, htype));
+
if (bufferIn.getLength() == bufferIn.getPosition()) {
// this is *NOT* DHCP packet. It does not have any DHCPv4 options. In
// particular, it does not have magic cookie, a 4 byte sequence that
@@ -189,21 +216,44 @@ Pkt4::unpack() {
}
void Pkt4::check() {
+ uint8_t msg_type = getType();
+ if (msg_type > DHCPLEASEACTIVE) {
+ isc_throw(BadValue, "Invalid DHCP message type received: "
+ << msg_type);
+ }
+}
+
+uint8_t Pkt4::getType() const {
+ OptionPtr generic = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (!generic) {
+ isc_throw(Unexpected, "Missing DHCP Message Type option");
+ }
+
+ // Check if Message Type is specified as OptionInt<uint8_t>
boost::shared_ptr<OptionInt<uint8_t> > typeOpt =
- boost::dynamic_pointer_cast<OptionInt<uint8_t> >(getOption(DHO_DHCP_MESSAGE_TYPE));
+ boost::dynamic_pointer_cast<OptionInt<uint8_t> >(generic);
if (typeOpt) {
- uint8_t msg_type = typeOpt->getValue();
- if (msg_type > DHCPLEASEACTIVE) {
- isc_throw(BadValue, "Invalid DHCP message type received: "
- << msg_type);
- }
- msg_type_ = msg_type;
+ return (typeOpt->getValue());
+ }
+ // Try to use it as generic option
+ return (generic->getUint8());
+}
+
+void Pkt4::setType(uint8_t dhcp_type) {
+ OptionPtr opt = getOption(DHO_DHCP_MESSAGE_TYPE);
+ if (opt) {
+ // There is message type option already, update it
+ opt->setUint8(dhcp_type);
} else {
- isc_throw(Unexpected, "Missing DHCP Message Type option");
+ // There is no message type option yet, add it
+ std::vector<uint8_t> tmp(1, dhcp_type);
+ opt = OptionPtr(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE, tmp));
+ addOption(opt);
}
}
+
void Pkt4::repack() {
bufferOut_.writeData(&data_[0], data_.size());
}
@@ -213,7 +263,7 @@ Pkt4::toText() {
stringstream tmp;
tmp << "localAddr=" << local_addr_.toText() << ":" << local_port_
<< " remoteAddr=" << remote_addr_.toText()
- << ":" << remote_port_ << ", msgtype=" << int(msg_type_)
+ << ":" << remote_port_ << ", msgtype=" << getType()
<< ", transid=0x" << hex << transid_ << dec << endl;
for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin();
@@ -239,10 +289,15 @@ Pkt4::setHWAddr(uint8_t hType, uint8_t hlen,
isc_throw(OutOfRange, "Invalid HW Address specified");
}
- htype_ = hType;
- hlen_ = hlen;
- std::copy(&mac_addr[0], &mac_addr[hlen], &chaddr_[0]);
- std::fill(&chaddr_[hlen], &chaddr_[MAX_CHADDR_LEN], 0);
+ hwaddr_.reset(new HWAddr(mac_addr, hType));
+}
+
+void
+Pkt4::setHWAddr(const HWAddrPtr& addr) {
+ if (!addr) {
+ isc_throw(BadValue, "Setting hw address to NULL is forbidden");
+ }
+ hwaddr_ = addr;
}
void
@@ -302,6 +357,23 @@ Pkt4::DHCPTypeToBootpType(uint8_t dhcpType) {
}
}
+uint8_t
+Pkt4::getHtype() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ return (hwaddr_->htype_);
+}
+
+uint8_t
+Pkt4::getHlen() const {
+ if (!hwaddr_) {
+ isc_throw(InvalidOperation, "Can't get HType. HWAddr not defined");
+ }
+ uint8_t len = hwaddr_->hwaddr_.size();
+ return (len <= MAX_CHADDR_LEN ? len : MAX_CHADDR_LEN);
+}
+
void
Pkt4::addOption(boost::shared_ptr<Option> opt) {
// Check for uniqueness (DHCPv4 options must be unique)
@@ -313,7 +385,7 @@ Pkt4::addOption(boost::shared_ptr<Option> opt) {
}
boost::shared_ptr<isc::dhcp::Option>
-Pkt4::getOption(uint8_t type) {
+Pkt4::getOption(uint8_t type) const {
Option::OptionCollection::const_iterator x = options_.find(type);
if (x != options_.end()) {
return (*x).second;
@@ -321,6 +393,16 @@ Pkt4::getOption(uint8_t type) {
return boost::shared_ptr<isc::dhcp::Option>(); // NULL
}
+bool
+Pkt4::delOption(uint8_t type) {
+ isc::dhcp::Option::OptionCollection::iterator x = options_.find(type);
+ if (x != options_.end()) {
+ options_.erase(x);
+ return (true); // delete successful
+ }
+ return (false); // can't find option to be deleted
+}
+
void
Pkt4::updateTimestamp() {
timestamp_ = boost::posix_time::microsec_clock::universal_time();
diff --git a/src/lib/dhcp/pkt4.h b/src/lib/dhcp/pkt4.h
index 5139458..664686b 100644
--- a/src/lib/dhcp/pkt4.h
+++ b/src/lib/dhcp/pkt4.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <util/buffer.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/shared_ptr.hpp>
@@ -217,16 +218,15 @@ public:
/// @return transaction-id
uint32_t getTransid() const { return (transid_); };
- /// @brief Returns message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Returns DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @return message type
- uint8_t
- getType() const { return (msg_type_); }
+ uint8_t getType() const;
- /// @brief Sets message type (e.g. 1 = DHCPDISCOVER).
+ /// @brief Sets DHCP message type (e.g. 1 = DHCPDISCOVER).
///
/// @param type message type to be set
- void setType(uint8_t type) { msg_type_=type; };
+ void setType(uint8_t type);
/// @brief Returns sname field
///
@@ -273,27 +273,28 @@ public:
void setHWAddr(uint8_t hType, uint8_t hlen,
const std::vector<uint8_t>& mac_addr);
+ /// @brief Sets hardware address
+ ///
+ /// Sets hardware address, based on existing HWAddr structure
+ /// @param addr already filled in HWAddr structure
+ /// @throw BadValue if addr is null
+ void setHWAddr(const HWAddrPtr& addr);
+
/// Returns htype field
///
/// @return hardware type
uint8_t
- getHtype() const { return (htype_); };
+ getHtype() const;
/// Returns hlen field
///
/// @return hardware address length
uint8_t
- getHlen() const { return (hlen_); };
-
- /// @brief Returns chaddr field.
- ///
- /// Note: This is 16 bytes long field. It doesn't have to be
- /// null-terminated. Do no use strlen() or similar on it.
- ///
- /// @return pointer to hardware address
- const uint8_t*
- getChaddr() const { return (chaddr_); };
+ getHlen() const;
+ /// @brief returns hardware address information
+ /// @return hardware address structure
+ HWAddrPtr getHWAddr() const { return (hwaddr_); }
/// @brief Returns reference to output buffer.
///
@@ -321,7 +322,12 @@ public:
/// @return returns option of requested type (or NULL)
/// if no such option is present
boost::shared_ptr<Option>
- getOption(uint8_t opt_type);
+ getOption(uint8_t opt_type) const;
+
+ /// @brief Deletes specified option
+ /// @param type option type to be deleted
+ /// @return true if anything was deleted, false otherwise
+ bool delOption(uint8_t type);
/// @brief Returns interface name.
///
@@ -454,11 +460,11 @@ protected:
/// type is kept in message type option).
uint8_t op_;
- /// link-layer address type
- uint8_t htype_;
-
- /// link-layer address length
- uint8_t hlen_;
+ /// @brief link-layer address and hardware information
+ /// represents 3 fields: htype (hardware type, 1 byte), hlen (length of the
+ /// hardware address, up to 16) and chaddr (hardware address field,
+ /// 16 bytes)
+ HWAddrPtr hwaddr_;
/// Number of relay agents traversed
uint8_t hops_;
@@ -484,9 +490,6 @@ protected:
/// giaddr field (32 bits): Gateway IP address
isc::asiolink::IOAddress giaddr_;
- /// Hardware address field (16 bytes)
- uint8_t chaddr_[MAX_CHADDR_LEN];
-
/// sname field (64 bytes)
uint8_t sname_[MAX_SNAME_LEN];
@@ -518,16 +521,11 @@ protected:
/// data format change etc.
std::vector<uint8_t> data_;
- /// message type (e.g. 1=DHCPDISCOVER)
- /// @todo this will eventually be replaced with DHCP Message Type
- /// option (option 53)
- uint8_t msg_type_;
-
/// collection of options present in this message
///
/// @warning This protected member is accessed by derived
/// classes directly. One of such derived classes is
- /// @ref perfdhcp::PerfPkt4. The impact on derived clasess'
+ /// @ref perfdhcp::PerfPkt4. The impact on derived classes'
/// behavior must be taken into consideration before making
/// changes to this member such as access scope restriction or
/// data format change etc.
diff --git a/src/lib/dhcp/tests/Makefile.am b/src/lib/dhcp/tests/Makefile.am
index 4d177d1..4833fb5 100644
--- a/src/lib/dhcp/tests/Makefile.am
+++ b/src/lib/dhcp/tests/Makefile.am
@@ -27,6 +27,7 @@ if HAVE_GTEST
TESTS += libdhcp++_unittests
libdhcp___unittests_SOURCES = run_unittests.cc
+libdhcp___unittests_SOURCES += hwaddr_unittest.cc
libdhcp___unittests_SOURCES += iface_mgr_unittest.cc
libdhcp___unittests_SOURCES += libdhcp++_unittest.cc
libdhcp___unittests_SOURCES += option4_addrlst_unittest.cc
diff --git a/src/lib/dhcp/tests/duid_unittest.cc b/src/lib/dhcp/tests/duid_unittest.cc
index 9db4c35..de20e51 100644
--- a/src/lib/dhcp/tests/duid_unittest.cc
+++ b/src/lib/dhcp/tests/duid_unittest.cc
@@ -108,6 +108,14 @@ TEST(DuidTest, getType) {
EXPECT_EQ(DUID::DUID_UNKNOWN, duid_invalid->getType());
}
+// Test checks if the toText() returns valid texual representation
+TEST(DuidTest, toText) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
+
+ DUID duid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+}
+
// This test checks if the comparison operators are sane.
TEST(DuidTest, operators) {
uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
@@ -174,8 +182,8 @@ TEST(ClientIdTest, operators) {
TEST(ClientIdTest, toText) {
uint8_t data1[] = {0, 1, 2, 3, 4, 0xff, 0xfe};
- DUID duid(data1, sizeof(data1));
- EXPECT_EQ("00:01:02:03:04:ff:fe", duid.toText());
+ ClientId clientid(data1, sizeof(data1));
+ EXPECT_EQ("00:01:02:03:04:ff:fe", clientid.toText());
}
} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/hwaddr_unittest.cc b/src/lib/dhcp/tests/hwaddr_unittest.cc
new file mode 100644
index 0000000..144d62d
--- /dev/null
+++ b/src/lib/dhcp/tests/hwaddr_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcp/hwaddr.h>
+#include <dhcp/dhcp4.h>
+#include <exceptions/exceptions.h>
+
+#include <boost/scoped_ptr.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::dhcp;
+using namespace isc::asiolink;
+
+using boost::scoped_ptr;
+
+namespace {
+
+// This test verifies if the constructors are working as expected
+// and process passed parameters.
+TEST(HWAddrTest, constructor) {
+
+ const uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ const uint8_t htype = HTYPE_ETHER;
+
+ vector<uint8_t> data2(data1, data1 + sizeof(data1));
+
+ scoped_ptr<HWAddr> hwaddr1(new HWAddr(data1, sizeof(data1), htype));
+ scoped_ptr<HWAddr> hwaddr2(new HWAddr(data2, htype));
+ scoped_ptr<HWAddr> hwaddr3(new HWAddr());
+
+ EXPECT_TRUE(data2 == hwaddr1->hwaddr_);
+ EXPECT_EQ(htype, hwaddr1->htype_);
+
+ EXPECT_TRUE(data2 == hwaddr2->hwaddr_);
+ EXPECT_EQ(htype, hwaddr2->htype_);
+
+ EXPECT_EQ(0, hwaddr3->hwaddr_.size());
+ EXPECT_EQ(htype, hwaddr3->htype_);
+}
+
+// This test checks if the comparison operators are sane.
+TEST(HWAddrTest, operators) {
+ uint8_t data1[] = {0, 1, 2, 3, 4, 5, 6};
+ uint8_t data2[] = {0, 1, 2, 3, 4};
+ uint8_t data3[] = {0, 1, 2, 3, 4, 5, 7}; // last digit different
+ uint8_t data4[] = {0, 1, 2, 3, 4, 5, 6}; // the same as 1
+
+ uint8_t htype1 = HTYPE_ETHER;
+ uint8_t htype2 = HTYPE_FDDI;
+
+ scoped_ptr<HWAddr> hw1(new HWAddr(data1, sizeof(data1), htype1));
+ scoped_ptr<HWAddr> hw2(new HWAddr(data2, sizeof(data2), htype1));
+ scoped_ptr<HWAddr> hw3(new HWAddr(data3, sizeof(data3), htype1));
+ scoped_ptr<HWAddr> hw4(new HWAddr(data4, sizeof(data4), htype1));
+
+ // MAC address the same as data1 and data4, but different hardware type
+ scoped_ptr<HWAddr> hw5(new HWAddr(data4, sizeof(data4), htype2));
+
+ EXPECT_TRUE(*hw1 == *hw4);
+ EXPECT_FALSE(*hw1 == *hw2);
+ EXPECT_FALSE(*hw1 == *hw3);
+
+ EXPECT_FALSE(*hw1 != *hw4);
+ EXPECT_TRUE(*hw1 != *hw2);
+ EXPECT_TRUE(*hw1 != *hw3);
+
+ EXPECT_FALSE(*hw1 == *hw5);
+ EXPECT_FALSE(*hw4 == *hw5);
+
+ EXPECT_TRUE(*hw1 != *hw5);
+ EXPECT_TRUE(*hw4 != *hw5);
+}
+
+// Checks that toText() method produces appropriate text representation
+TEST(HWAddrTest, toText) {
+ uint8_t data[] = {0, 1, 2, 3, 4, 5};
+ uint8_t htype = 15;
+
+ HWAddrPtr hw(new HWAddr(data, sizeof(data), htype));
+
+ EXPECT_EQ("hwtype=15 00:01:02:03:04:05", hw->toText());
+
+}
+
+TEST(HWAddrTest, stringConversion) {
+
+ // Check that an empty vector returns an appropriate string
+ HWAddr hwaddr;
+ std::string result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 "), result);
+
+ // ... that a single-byte string is OK
+ hwaddr.hwaddr_.push_back(0xc3);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3"), result);
+
+ // ... and that a multi-byte string works
+ hwaddr.hwaddr_.push_back(0x7);
+ hwaddr.hwaddr_.push_back(0xa2);
+ hwaddr.hwaddr_.push_back(0xe8);
+ hwaddr.hwaddr_.push_back(0x42);
+ result = hwaddr.toText();
+ EXPECT_EQ(std::string("hwtype=1 c3:07:a2:e8:42"), result);
+}
+
+
+} // end of anonymous namespace
diff --git a/src/lib/dhcp/tests/iface_mgr_unittest.cc b/src/lib/dhcp/tests/iface_mgr_unittest.cc
index 11cbf34..d72ef9e 100644
--- a/src/lib/dhcp/tests/iface_mgr_unittest.cc
+++ b/src/lib/dhcp/tests/iface_mgr_unittest.cc
@@ -702,11 +702,9 @@ TEST_F(IfaceMgrTest, sendReceive4) {
sendPkt->setYiaddr(IOAddress("192.0.2.3"));
sendPkt->setGiaddr(IOAddress("192.0.2.4"));
- // unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present
- boost::shared_ptr<Option> msgType(new Option(Option::V4,
- static_cast<uint16_t>(DHO_DHCP_MESSAGE_TYPE)));
- msgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
- sendPkt->addOption(msgType);
+ // Unpack() now checks if mandatory DHCP_MESSAGE_TYPE is present.
+ // Workarounds (creating DHCP Message Type Option by hand) are no longer
+ // needed as setDhcpType() is called in constructor.
uint8_t sname[] = "That's just a string that will act as SNAME";
sendPkt->setSname(sname, strlen((const char*)sname));
@@ -744,7 +742,6 @@ TEST_F(IfaceMgrTest, sendReceive4) {
EXPECT_EQ(sendPkt->getYiaddr(), rcvPkt->getYiaddr());
EXPECT_EQ(sendPkt->getGiaddr(), rcvPkt->getGiaddr());
EXPECT_EQ(sendPkt->getTransid(), rcvPkt->getTransid());
- EXPECT_EQ(sendPkt->getType(), rcvPkt->getType());
EXPECT_TRUE(sendPkt->getSname() == rcvPkt->getSname());
EXPECT_TRUE(sendPkt->getFile() == rcvPkt->getFile());
EXPECT_EQ(sendPkt->getHtype(), rcvPkt->getHtype());
diff --git a/src/lib/dhcp/tests/libdhcp++_unittest.cc b/src/lib/dhcp/tests/libdhcp++_unittest.cc
index e44ef58..a59da12 100644
--- a/src/lib/dhcp/tests/libdhcp++_unittest.cc
+++ b/src/lib/dhcp/tests/libdhcp++_unittest.cc
@@ -453,6 +453,66 @@ TEST_F(LibDhcpTest, unpackOptions4) {
EXPECT_TRUE(x == options.end()); // option 2 not found
}
+TEST_F(LibDhcpTest, isStandardOption4) {
+ // Get all option codes that are not occupied by standard options.
+ const uint16_t unassigned_codes[] = { 84, 96, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 115, 126, 127, 147, 148, 149,
+ 178, 179, 180, 181, 182, 183, 184, 185, 186,
+ 187, 188, 189, 190, 191, 192, 193, 194, 195,
+ 196, 197, 198, 199, 200, 201, 202, 203, 204,
+ 205, 206, 207, 214, 215, 216, 217, 218, 219,
+ 222, 223 };
+ const size_t unassigned_num = sizeof(unassigned_codes) / sizeof(unassigned_codes[0]);
+
+ // Try all possible option codes.
+ for (size_t i = 0; i < 256; ++i) {
+ // Some ranges of option codes are unassigned and thus the isStandardOption
+ // should return false for them.
+ bool check_unassigned = false;
+ // Check the array of unassigned options to find out whether option code
+ // is assigned to standard option or unassigned.
+ for (size_t j = 0; j < unassigned_num; ++j) {
+ // If option code is found within the array of unassigned options
+ // we the isStandardOption function should return false.
+ if (unassigned_codes[j] == i) {
+ check_unassigned = true;
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V4,
+ unassigned_codes[j]))
+ << "Test failed for option code " << unassigned_codes[j];
+ break;
+ }
+ }
+ // If the option code belongs to the standard option then the
+ // isStandardOption should return true.
+ if (!check_unassigned) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V4, i))
+ << "Test failed for the option code " << i;
+ }
+ }
+}
+
+TEST_F(LibDhcpTest, isStandardOption6) {
+ // All option codes in the range from 0 to 78 (except 10 and 35)
+ // identify the standard options.
+ for (uint16_t code = 0; code < 79; ++code) {
+ if (code != 10 && code != 35) {
+ EXPECT_TRUE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+ }
+
+ // Check the option codes 10 and 35. They are unassigned.
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 10));
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, 35));
+
+ // Check a range of option codes above 78. Those are option codes
+ // identifying non-standard options.
+ for (uint16_t code = 79; code < 512; ++code) {
+ EXPECT_FALSE(LibDHCP::isStandardOption(Option::V6, code))
+ << "Test failed for option code " << code;
+ }
+}
+
TEST_F(LibDhcpTest, stdOptionDefs4) {
// Create a buffer that holds dummy option data.
diff --git a/src/lib/dhcp/tests/option_definition_unittest.cc b/src/lib/dhcp/tests/option_definition_unittest.cc
index deb61b5..310c7bf 100644
--- a/src/lib/dhcp/tests/option_definition_unittest.cc
+++ b/src/lib/dhcp/tests/option_definition_unittest.cc
@@ -164,29 +164,55 @@ TEST_F(OptionDefinitionTest, validate) {
EXPECT_THROW(opt_def5.validate(), MalformedOptionDefinition);
// Option name must not contain spaces.
- OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string", true);
+ OptionDefinition opt_def6("OPTION CLIENTID", D6O_CLIENTID, "string");
EXPECT_THROW(opt_def6.validate(), MalformedOptionDefinition);
+ // Option name may contain lower case letters.
+ OptionDefinition opt_def7("option_clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def7.validate());
+
+ // Using digits in option name is legal.
+ OptionDefinition opt_def8("option_123", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def8.validate());
+
+ // Using hyphen is legal.
+ OptionDefinition opt_def9("option-clientid", D6O_CLIENTID, "string");
+ EXPECT_NO_THROW(opt_def9.validate());
+
+ // Using hyphen or undescore at the beginning or at the end
+ // of the option name is not allowed.
+ OptionDefinition opt_def10("-option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def10.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def11("_option-clientid", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def11.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def12("option-clientid_", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def12.validate(), MalformedOptionDefinition);
+
+ OptionDefinition opt_def13("option-clientid-", D6O_CLIENTID, "string");
+ EXPECT_THROW(opt_def13.validate(), MalformedOptionDefinition);
+
// Having array of strings does not make sense because there is no way
// to determine string's length.
- OptionDefinition opt_def7("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
- EXPECT_THROW(opt_def7.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def14("OPTION_CLIENTID", D6O_CLIENTID, "string", true);
+ EXPECT_THROW(opt_def14.validate(), MalformedOptionDefinition);
// It does not make sense to have string field within the record before
// other fields because there is no way to determine the length of this
// string and thus there is no way to determine where the other field
// begins.
- OptionDefinition opt_def8("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def8.addRecordField("string");
- opt_def8.addRecordField("uint16");
- EXPECT_THROW(opt_def8.validate(), MalformedOptionDefinition);
+ OptionDefinition opt_def15("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def15.addRecordField("string");
+ opt_def15.addRecordField("uint16");
+ EXPECT_THROW(opt_def15.validate(), MalformedOptionDefinition);
// ... but it is ok if the string value is the last one.
- OptionDefinition opt_def9("OPTION_STATUS_CODE", D6O_STATUS_CODE,
- "record");
- opt_def9.addRecordField("uint8");
- opt_def9.addRecordField("string");
+ OptionDefinition opt_def16("OPTION_STATUS_CODE", D6O_STATUS_CODE,
+ "record");
+ opt_def16.addRecordField("uint8");
+ opt_def16.addRecordField("string");
}
diff --git a/src/lib/dhcp/tests/pkt4_unittest.cc b/src/lib/dhcp/tests/pkt4_unittest.cc
index bd848ff..49588e1 100644
--- a/src/lib/dhcp/tests/pkt4_unittest.cc
+++ b/src/lib/dhcp/tests/pkt4_unittest.cc
@@ -69,8 +69,9 @@ TEST(Pkt4Test, constructor) {
pkt = new Pkt4(DHCPDISCOVER, 0xffffffff);
);
- // DHCPv4 packet must be at least 236 bytes long
- EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // DHCPv4 packet must be at least 236 bytes long, with Message Type
+ // Option taking extra 3 bytes it is 239
+ EXPECT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
EXPECT_EQ(DHCPDISCOVER, pkt->getType());
EXPECT_EQ(0xffffffff, pkt->getTransid());
EXPECT_NO_THROW(
@@ -219,8 +220,10 @@ TEST(Pkt4Test, fixedFields) {
EXPECT_EQ(dummySiaddr.toText(), pkt->getSiaddr().toText());
EXPECT_EQ(dummyGiaddr.toText(), pkt->getGiaddr().toText());
- // chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), 16));
+ // Chaddr contains link-layer addr (MAC). It is no longer always 16 bytes
+ // long and its length depends on hlen value (it is up to 16 bytes now).
+ ASSERT_EQ(pkt->getHWAddr()->hwaddr_.size(), dummyHlen);
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], 64));
@@ -237,7 +240,9 @@ TEST(Pkt4Test, fixedFieldsPack) {
pkt->pack();
);
- ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN), pkt->len());
+ // Minimum packet size is 236 bytes + 3 bytes of mandatory
+ // DHCP Message Type Option
+ ASSERT_EQ(static_cast<size_t>(Pkt4::DHCPV4_PKT_HDR_LEN) + 3, pkt->len());
// redundant but MUCH easier for debug in gdb
const uint8_t* exp = &expectedFormat[0];
@@ -282,7 +287,7 @@ TEST(Pkt4Test, fixedFieldsUnpack) {
EXPECT_EQ(string("255.255.255.255"), pkt->getGiaddr().toText());
// chaddr is always 16 bytes long and contains link-layer addr (MAC)
- EXPECT_EQ(0, memcmp(dummyChaddr, pkt->getChaddr(), Pkt4::MAX_CHADDR_LEN));
+ EXPECT_EQ(0, memcmp(dummyChaddr, &pkt->getHWAddr()->hwaddr_[0], dummyHlen));
ASSERT_EQ(static_cast<size_t>(Pkt4::MAX_SNAME_LEN), pkt->getSname().size());
EXPECT_EQ(0, memcmp(dummySname, &pkt->getSname()[0], Pkt4::MAX_SNAME_LEN));
@@ -321,7 +326,7 @@ TEST(Pkt4Test, hwAddr) {
pkt->setHWAddr(255-macLen*10, // just weird htype
macLen,
mac);
- EXPECT_EQ(0, memcmp(expectedChaddr, pkt->getChaddr(),
+ EXPECT_EQ(0, memcmp(expectedChaddr, &pkt->getHWAddr()->hwaddr_[0],
Pkt4::MAX_CHADDR_LEN));
EXPECT_NO_THROW(
@@ -469,7 +474,7 @@ TEST(Pkt4Test, file) {
static uint8_t v4Opts[] = {
12, 3, 0, 1, 2, // Hostname
14, 3, 10, 11, 12, // Merit Dump File
- 53, 1, 1, // Message Type (required to not throw exception during unpack)
+ 53, 1, 2, // Message Type (required to not throw exception during unpack)
60, 3, 20, 21, 22, // Class Id
128, 3, 30, 31, 32, // Vendor specific
254, 3, 40, 41, 42, // Reserved
@@ -487,18 +492,15 @@ TEST(Pkt4Test, options) {
boost::shared_ptr<Option> opt1(new Option(Option::V4, 12, payload[0]));
boost::shared_ptr<Option> opt3(new Option(Option::V4, 14, payload[1]));
- boost::shared_ptr<Option> optMsgType(new Option(Option::V4, DHO_DHCP_MESSAGE_TYPE));
boost::shared_ptr<Option> opt2(new Option(Option::V4, 60, payload[2]));
boost::shared_ptr<Option> opt5(new Option(Option::V4,128, payload[3]));
boost::shared_ptr<Option> opt4(new Option(Option::V4,254, payload[4]));
- optMsgType->setUint8(static_cast<uint8_t>(DHCPDISCOVER));
pkt->addOption(opt1);
pkt->addOption(opt2);
pkt->addOption(opt3);
pkt->addOption(opt4);
pkt->addOption(opt5);
- pkt->addOption(optMsgType);
EXPECT_TRUE(pkt->getOption(12));
EXPECT_TRUE(pkt->getOption(60));
@@ -530,6 +532,12 @@ TEST(Pkt4Test, options) {
EXPECT_EQ(0, memcmp(ptr, v4Opts, sizeof(v4Opts)));
EXPECT_EQ(DHO_END, static_cast<uint8_t>(*(ptr + sizeof(v4Opts))));
+ // delOption() checks
+ EXPECT_TRUE(pkt->getOption(12)); // Sanity check: option 12 is still there
+ EXPECT_TRUE(pkt->delOption(12)); // We should be able to remove it
+ EXPECT_FALSE(pkt->getOption(12)); // It should not be there anymore
+ EXPECT_FALSE(pkt->delOption(12)); // And removal should fail
+
EXPECT_NO_THROW(
delete pkt;
);
@@ -643,6 +651,23 @@ TEST(Pkt4Test, Timestamp) {
EXPECT_TRUE(ts_period.length().total_microseconds() >= 0);
}
+TEST(Pkt4Test, hwaddr) {
+ scoped_ptr<Pkt4> pkt(new Pkt4(DHCPOFFER, 1234));
+ const uint8_t hw[] = { 2, 4, 6, 8, 10, 12 }; // MAC
+ const uint8_t hw_type = 123; // hardware type
+
+ HWAddrPtr hwaddr(new HWAddr(hw, sizeof(hw), hw_type));
+ // setting NULL hardware address is not allowed
+ EXPECT_THROW(pkt->setHWAddr(HWAddrPtr()), BadValue);
+
+ pkt->setHWAddr(hwaddr);
+
+ EXPECT_EQ(hw_type, pkt->getHtype());
+
+ EXPECT_EQ(sizeof(hw), pkt->getHlen());
+
+ EXPECT_TRUE(hwaddr == pkt->getHWAddr());
+}
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am
index b44fa7d..ce066e7 100644
--- a/src/lib/dhcpsrv/Makefile.am
+++ b/src/lib/dhcpsrv/Makefile.am
@@ -1,6 +1,6 @@
SUBDIRS = . tests
-AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
+AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib -DDHCP_DATA_DIR="\"$(localstatedir)\""
AM_CPPFLAGS += $(BOOST_INCLUDES)
if HAVE_MYSQL
AM_CPPFLAGS += $(MYSQL_CPPFLAGS)
@@ -8,28 +8,48 @@ endif
AM_CXXFLAGS = $(B10_CXXFLAGS)
+# Define rule to build logging source files from message file
+dhcpsrv_messages.h dhcpsrv_messages.cc: dhcpsrv_messages.mes
+ $(top_builddir)/src/lib/log/compiler/message $(top_srcdir)/src/lib/dhcpsrv/dhcpsrv_messages.mes
+
+# Tell Automake that the dhcpsrv_messages.{cc,h} source files are created in the
+# build process, so it must create these before doing anything else. Although
+# they are a dependency of the library (so will be created from the message file
+# anyway), there is no guarantee as to exactly _when_ in the build they will be
+# created. As the .h file is included in other sources file (so must be
+# present when they are compiled), the safest option is to create it first.
+BUILT_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
+
# Some versions of GCC warn about some versions of Boost regarding
# missing initializer for members in its posix_time.
# https://svn.boost.org/trac/boost/ticket/3477
# But older GCC compilers don't have the flag.
AM_CXXFLAGS += $(WARNING_NO_MISSING_FIELD_INITIALIZERS_CFLAG)
-CLEANFILES = *.gcno *.gcda
+# Make sure the generated files are deleted in a "clean" operation
+CLEANFILES = *.gcno *.gcda dhcpsrv_messages.h dhcpsrv_messages.cc
lib_LTLIBRARIES = libb10-dhcpsrv.la
libb10_dhcpsrv_la_SOURCES =
libb10_dhcpsrv_la_SOURCES += addr_utilities.cc addr_utilities.h
libb10_dhcpsrv_la_SOURCES += alloc_engine.cc alloc_engine.h
+libb10_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
libb10_dhcpsrv_la_SOURCES += cfgmgr.cc cfgmgr.h
+libb10_dhcpsrv_la_SOURCES += dhcp_config_parser.h
libb10_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
libb10_dhcpsrv_la_SOURCES += lease_mgr_factory.cc lease_mgr_factory.h
libb10_dhcpsrv_la_SOURCES += memfile_lease_mgr.cc memfile_lease_mgr.h
if HAVE_MYSQL
libb10_dhcpsrv_la_SOURCES += mysql_lease_mgr.cc mysql_lease_mgr.h
endif
+libb10_dhcpsrv_la_SOURCES += option_space.cc option_space.h
+libb10_dhcpsrv_la_SOURCES += option_space_container.h
libb10_dhcpsrv_la_SOURCES += pool.cc pool.h
libb10_dhcpsrv_la_SOURCES += subnet.cc subnet.h
libb10_dhcpsrv_la_SOURCES += triplet.h
+libb10_dhcpsrv_la_SOURCES += utils.h
+
+nodist_libb10_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
libb10_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
libb10_dhcpsrv_la_CPPFLAGS = $(AM_CPPFLAGS) $(LOG4CPLUS_INCLUDES)
@@ -47,6 +67,9 @@ if USE_CLANGPP
libb10_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
endif
+# The message file should be in the distribution
+EXTRA_DIST = dhcpsrv_messages.mes
+
# Distribute MySQL schema creation script and backend documentation
-EXTRA_DIST = dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
+EXTRA_DIST += dhcpdb_create.mysql database_backends.dox libdhcpsrv.dox
dist_pkgdata_DATA = dhcpdb_create.mysql
diff --git a/src/lib/dhcpsrv/addr_utilities.cc b/src/lib/dhcpsrv/addr_utilities.cc
index 98fbeb9..d32c209 100644
--- a/src/lib/dhcpsrv/addr_utilities.cc
+++ b/src/lib/dhcpsrv/addr_utilities.cc
@@ -195,5 +195,15 @@ isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix
}
}
+isc::asiolink::IOAddress getNetmask4(uint8_t len) {
+ if (len > 32) {
+ isc_throw(BadValue, "Invalid netmask size " << len << ", allowed range "
+ "is 0..32");
+ }
+ uint32_t x = ~bitMask4[len];
+
+ return (IOAddress(x));
+}
+
};
};
diff --git a/src/lib/dhcpsrv/addr_utilities.h b/src/lib/dhcpsrv/addr_utilities.h
index 784aeab..6aef574 100644
--- a/src/lib/dhcpsrv/addr_utilities.h
+++ b/src/lib/dhcpsrv/addr_utilities.h
@@ -52,6 +52,11 @@ isc::asiolink::IOAddress firstAddrInPrefix(const isc::asiolink::IOAddress& prefi
isc::asiolink::IOAddress lastAddrInPrefix(const isc::asiolink::IOAddress& prefix,
uint8_t len);
+/// @brief generates an IPv4 netmask of specified length
+/// @throw BadValue if len is greater than 32
+/// @return netmask
+isc::asiolink::IOAddress getNetmask4(uint8_t len);
+
};
};
diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc
index 1c64c04..7a64dac 100644
--- a/src/lib/dhcpsrv/alloc_engine.cc
+++ b/src/lib/dhcpsrv/alloc_engine.cc
@@ -16,7 +16,7 @@
#include <dhcpsrv/lease_mgr_factory.h>
#include <cstring>
-
+#include <vector>
#include <string.h>
using namespace isc::asiolink;
@@ -58,7 +58,7 @@ AllocEngine::IterativeAllocator::increaseAddress(const isc::asiolink::IOAddress&
isc::asiolink::IOAddress
-AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
+AllocEngine::IterativeAllocator::pickAddress(const SubnetPtr& subnet,
const DuidPtr&,
const IOAddress&) {
@@ -67,14 +67,14 @@ AllocEngine::IterativeAllocator::pickAddress(const Subnet6Ptr& subnet,
// perhaps restaring the server).
IOAddress last = subnet->getLastAllocated();
- const Pool6Collection& pools = subnet->getPools();
+ const PoolCollection& pools = subnet->getPools();
if (pools.empty()) {
isc_throw(AllocFailed, "No pools defined in selected subnet");
}
// first we need to find a pool the last address belongs to.
- Pool6Collection::const_iterator it;
+ PoolCollection::const_iterator it;
for (it = pools.begin(); it != pools.end(); ++it) {
if ((*it)->inRange(last)) {
break;
@@ -124,9 +124,9 @@ AllocEngine::HashedAllocator::HashedAllocator()
isc::asiolink::IOAddress
-AllocEngine::HashedAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::HashedAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Hashed allocator is not implemented");
}
@@ -137,9 +137,9 @@ AllocEngine::RandomAllocator::RandomAllocator()
isc::asiolink::IOAddress
-AllocEngine::RandomAllocator::pickAddress(const Subnet6Ptr&,
- const DuidPtr&,
- const IOAddress&) {
+AllocEngine::RandomAllocator::pickAddress(const SubnetPtr&,
+ const DuidPtr&,
+ const IOAddress&) {
isc_throw(NotImplemented, "Random allocator is not implemented");
}
@@ -191,7 +191,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
/// implemented
// the hint is valid and not currently used, let's create a lease for it
- Lease6Ptr lease = createLease(subnet, duid, iaid, hint, fake_allocation);
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, hint, fake_allocation);
// It can happen that the lease allocation failed (we could have lost
// the race condition. That means that the hint is lo longer usable and
@@ -235,7 +235,7 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
if (!existing) {
// there's no existing lease for selected candidate, so it is
// free. Let's allocate it.
- Lease6Ptr lease = createLease(subnet, duid, iaid, candidate,
+ Lease6Ptr lease = createLease6(subnet, duid, iaid, candidate,
fake_allocation);
if (lease) {
return (lease);
@@ -260,6 +260,146 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet,
<< " tries");
}
+Lease4Ptr
+AllocEngine::allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& hint,
+ bool fake_allocation /* = false */ ) {
+
+ // Allocator is always created in AllocEngine constructor and there is
+ // currently no other way to set it, so that check is not really necessary.
+ if (!allocator_) {
+ isc_throw(InvalidOperation, "No allocator selected");
+ }
+
+ // Check if there's existing lease for that subnet/clientid/hwaddr combination.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID());
+ if (existing) {
+ // We have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ if (existing) {
+ return (existing);
+ }
+
+ // If renewal failed (e.g. the lease no longer matches current configuration)
+ // let's continue the allocation process
+ }
+
+ if (clientid) {
+ existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID());
+ if (existing) {
+ // we have a lease already. This is a returning client, probably after
+ // its reboot.
+ existing = renewLease4(subnet, clientid, hwaddr, existing, fake_allocation);
+ // @todo: produce a warning. We haven't found him using MAC address, but
+ // we found him using client-id
+ if (existing) {
+ return (existing);
+ }
+ }
+ }
+
+ // check if the hint is in pool and is available
+ if (subnet->inPool(hint)) {
+ existing = LeaseMgrFactory::instance().getLease4(hint);
+ if (!existing) {
+ /// @todo: Check if the hint is reserved once we have host support
+ /// implemented
+
+ // The hint is valid and not currently used, let's create a lease for it
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation);
+
+ // It can happen that the lease allocation failed (we could have lost
+ // the race condition. That means that the hint is lo longer usable and
+ // we need to continue the regular allocation path.
+ if (lease) {
+ return (lease);
+ }
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+
+ }
+ }
+
+ // Hint is in the pool but is not available. Search the pool until first of
+ // the following occurs:
+ // - we find a free address
+ // - we find an address for which the lease has expired
+ // - we exhaust the number of tries
+ //
+ // @todo: Current code does not handle pool exhaustion well. It will be
+ // improved. Current problems:
+ // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g.
+ // 10 addresses), we will iterate over it 100 times before giving up
+ // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite)
+ // 3. the whole concept of infinite attempts is just asking for infinite loop
+ // We may consider some form or reference counting (this pool has X addresses
+ // left), but this has one major problem. We exactly control allocation
+ // moment, but we currently do not control expiration time at all
+
+ unsigned int i = attempts_;
+ do {
+ IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint);
+
+ /// @todo: check if the address is reserved once we have host support
+ /// implemented
+
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate);
+ if (!existing) {
+ // there's no existing lease for selected candidate, so it is
+ // free. Let's allocate it.
+ Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate,
+ fake_allocation);
+ if (lease) {
+ return (lease);
+ }
+
+ // Although the address was free just microseconds ago, it may have
+ // been taken just now. If the lease insertion fails, we continue
+ // allocation attempts.
+ } else {
+ if (existing->expired()) {
+ return (reuseExpiredLease(existing, subnet, clientid, hwaddr,
+ fake_allocation));
+ }
+ }
+
+ // Continue trying allocation until we run out of attempts
+ // (or attempts are set to 0, which means infinite)
+ --i;
+ } while ( i || !attempts_);
+
+ isc_throw(AllocFailed, "Failed to allocate address after " << attempts_
+ << " tries");
+}
+
+Lease4Ptr AllocEngine::renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */) {
+
+ lease->subnet_id_ = subnet->getID();
+ lease->hwaddr_ = hwaddr->hwaddr_;
+ lease->client_id_ = clientid;
+ lease->cltt_ = time(NULL);
+ lease->t1_ = subnet->getT1();
+ lease->t2_ = subnet->getT2();
+ lease->valid_lft_ = subnet->getValid();
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(lease);
+ }
+
+ return (lease);
+}
+
Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
const Subnet6Ptr& subnet,
const DuidPtr& duid,
@@ -300,11 +440,50 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired,
return (expired);
}
-Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
- const DuidPtr& duid,
- uint32_t iaid,
- const IOAddress& addr,
- bool fake_allocation /*= false */ ) {
+Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired,
+ const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation /*= false */ ) {
+
+ if (!expired->expired()) {
+ isc_throw(BadValue, "Attempt to recycle lease that is still valid");
+ }
+
+ // address, lease type and prefixlen (0) stay the same
+ expired->client_id_ = clientid;
+ expired->hwaddr_ = hwaddr->hwaddr_;
+ expired->valid_lft_ = subnet->getValid();
+ expired->t1_ = subnet->getT1();
+ expired->t2_ = subnet->getT2();
+ expired->cltt_ = time(NULL);
+ expired->subnet_id_ = subnet->getID();
+ expired->fixed_ = false;
+ expired->hostname_ = std::string("");
+ expired->fqdn_fwd_ = false;
+ expired->fqdn_rev_ = false;
+
+ /// @todo: log here that the lease was reused (there's ticket #2524 for
+ /// logging in libdhcpsrv)
+
+ if (!fake_allocation) {
+ // for REQUEST we do update the lease
+ LeaseMgrFactory::instance().updateLease4(expired);
+ }
+
+ // We do nothing for SOLICIT. We'll just update database when
+ // the client gets back to us with REQUEST message.
+
+ // it's not really expired at this stage anymore - let's return it as
+ // an updated lease
+ return (expired);
+}
+
+Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet,
+ const DuidPtr& duid,
+ uint32_t iaid,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
Lease6Ptr lease(new Lease6(Lease6::LEASE_IA_NA, addr, duid, iaid,
subnet->getPreferred(), subnet->getValid(),
@@ -338,6 +517,53 @@ Lease6Ptr AllocEngine::createLease(const Subnet6Ptr& subnet,
}
}
+Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet,
+ const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const IOAddress& addr,
+ bool fake_allocation /*= false */ ) {
+ if (!hwaddr) {
+ isc_throw(BadValue, "Can't create a lease with NULL HW address");
+ }
+ time_t now = time(NULL);
+
+ // @todo: remove this kludge after ticket #2590 is implemented
+ std::vector<uint8_t> local_copy;
+ if (clientid) {
+ local_copy = clientid->getDuid();
+ }
+
+ Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(),
+ &local_copy[0], local_copy.size(), subnet->getValid(),
+ subnet->getT1(), subnet->getT2(), now,
+ subnet->getID()));
+
+ if (!fake_allocation) {
+ // That is a real (REQUEST) allocation
+ bool status = LeaseMgrFactory::instance().addLease(lease);
+ if (status) {
+ return (lease);
+ } else {
+ // One of many failures with LeaseMgr (e.g. lost connection to the
+ // database, database failed etc.). One notable case for that
+ // is that we are working in multi-process mode and we lost a race
+ // (some other process got that address first)
+ return (Lease4Ptr());
+ }
+ } else {
+ // That is only fake (DISCOVER) allocation
+
+ // It is for OFFER only. We should not insert the lease into LeaseMgr,
+ // but rather check that we could have inserted it.
+ Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr);
+ if (!existing) {
+ return (lease);
+ } else {
+ return (Lease4Ptr());
+ }
+ }
+}
+
AllocEngine::~AllocEngine() {
// no need to delete allocator. smart_ptr will do the trick for us
}
diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h
index eab6e12..c6cbc35 100644
--- a/src/lib/dhcpsrv/alloc_engine.h
+++ b/src/lib/dhcpsrv/alloc_engine.h
@@ -17,6 +17,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/lease_mgr.h>
@@ -65,8 +66,14 @@ protected:
/// reserved - AllocEngine will check that and will call pickAddress
/// again if necessary. The number of times this method is called will
/// increase as the number of available leases will decrease.
+ ///
+ /// @param subnet next address will be returned from pool of that subnet
+ /// @param duid Client's DUID
+ /// @param hint client's hint
+ ///
+ /// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint) = 0;
/// @brief virtual destructor
@@ -96,7 +103,7 @@ protected:
/// @param hint client's hint (ignored)
/// @return the next address
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet,
+ pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
private:
@@ -125,7 +132,7 @@ protected:
/// @param duid Client's DUID
/// @param hint a hint (last address that was picked)
/// @return selected address
- virtual isc::asiolink::IOAddress pickAddress(const Subnet6Ptr& subnet,
+ virtual isc::asiolink::IOAddress pickAddress(const SubnetPtr& subnet,
const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -148,7 +155,7 @@ protected:
/// @param hint the last address that was picked (ignored)
/// @return a random address from the pool
virtual isc::asiolink::IOAddress
- pickAddress(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ pickAddress(const SubnetPtr& subnet, const DuidPtr& duid,
const isc::asiolink::IOAddress& hint);
};
@@ -174,6 +181,48 @@ protected:
/// we give up (0 means unlimited)
AllocEngine(AllocType engine_type, unsigned int attempts);
+ /// @brief Allocates an IPv4 lease
+ ///
+ /// This method uses currently selected allocator to pick an address from
+ /// specified subnet, creates a lease for that address and then inserts
+ /// it into LeaseMgr (if this allocation is not fake).
+ ///
+ /// @param subnet subnet the allocation should come from
+ /// @param clientid Client identifier
+ /// @param hwaddr client's hardware address info
+ /// @param hint a hint that the client provided
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return Allocated IPv4 lease (or NULL if allocation failed)
+ Lease4Ptr
+ allocateAddress4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& hint,
+ bool fake_allocation);
+
+ /// @brief Renews a IPv4 lease
+ ///
+ /// Since both request and renew are implemented in DHCPv4 as the sending of
+ /// a REQUEST packet, it is difficult to easily distinguish between those
+ /// cases. Therefore renew for DHCPv4 is done in the allocation engine.
+ /// This method is also used when client crashed/rebooted and tries
+ /// to get a new lease. It thinks that it gets a new lease, but in fact
+ /// we are only renewing the still valid lease for that client.
+ ///
+ /// @param subnet subnet the client is attached to
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param lease lease to be renewed
+ /// @param renewed lease (typically the same passed as lease parameter)
+ /// or NULL if the lease cannot be renewed
+ Lease4Ptr
+ renewLease4(const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const Lease4Ptr& lease,
+ bool fake_allocation /* = false */);
+
/// @brief Allocates an IPv6 lease
///
/// This method uses currently selected allocator to pick an address from
@@ -181,7 +230,7 @@ protected:
/// it into LeaseMgr (if this allocation is not fake).
///
/// @param subnet subnet the allocation should come from
- /// @param duid Client'd DUID
+ /// @param duid Client's DUID
/// @param iaid iaid field from the IA_NA container that client sent
/// @param hint a hint that the client provided
/// @param fake_allocation is this real i.e. REQUEST (false) or just picking
@@ -198,6 +247,25 @@ protected:
virtual ~AllocEngine();
private:
+ /// @brief Creates a lease and inserts it in LeaseMgr if necessary
+ ///
+ /// Creates a lease based on specified parameters and tries to insert it
+ /// into the database. That may fail in some cases, e.g. when there is another
+ /// allocation process and we lost a race to a specific lease.
+ ///
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param addr an address that was selected and is confirmed to be available
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return allocated lease (or NULL in the unlikely case of the lease just
+ /// becomed unavailable)
+ Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
/// @brief creates a lease and inserts it in LeaseMgr if necessary
///
/// Creates a lease based on specified parameters and tries to insert it
@@ -212,11 +280,30 @@ private:
/// an address for SOLICIT that is not really allocated (true)
/// @return allocated lease (or NULL in the unlikely case of the lease just
/// becomed unavailable)
- Lease6Ptr createLease(const Subnet6Ptr& subnet, const DuidPtr& duid,
- uint32_t iaid, const isc::asiolink::IOAddress& addr,
- bool fake_allocation = false);
+ Lease6Ptr createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid,
+ uint32_t iaid, const isc::asiolink::IOAddress& addr,
+ bool fake_allocation = false);
+
+ /// @brief Reuses expired IPv4 lease
+ ///
+ /// Updates existing expired lease with new information. Lease database
+ /// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
+ /// dummy allocation request (i.e. DISCOVER, fake_allocation = true).
+ ///
+ /// @param expired old, expired lease
+ /// @param subnet subnet the lease is allocated from
+ /// @param clientid client identifier
+ /// @param hwaddr client's hardware address
+ /// @param fake_allocation is this real i.e. REQUEST (false) or just picking
+ /// an address for DISCOVER that is not really allocated (true)
+ /// @return refreshed lease
+ /// @throw BadValue if trying to recycle lease that is still valid
+ Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet,
+ const ClientIdPtr& clientid,
+ const HWAddrPtr& hwaddr,
+ bool fake_allocation = false);
- /// @brief reuses expired lease
+ /// @brief Reuses expired IPv6 lease
///
/// Updates existing expired lease with new information. Lease database
/// is updated if this is real (i.e. REQUEST, fake_allocation = false), not
diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc
index 7dc5f55..b5e83e3 100644
--- a/src/lib/dhcpsrv/cfgmgr.cc
+++ b/src/lib/dhcpsrv/cfgmgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -13,7 +13,9 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <asiolink/io_address.h>
+#include <dhcp/libdhcp++.h>
#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
using namespace isc::asiolink;
using namespace isc::util;
@@ -21,15 +23,125 @@ using namespace isc::util;
namespace isc {
namespace dhcp {
-
-
-
CfgMgr&
CfgMgr::instance() {
static CfgMgr cfg_mgr;
return (cfg_mgr);
}
+void
+CfgMgr::addOptionSpace4(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces4_.find(space->getName());
+ if (it != spaces4_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces4_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionSpace6(const OptionSpacePtr& space) {
+ if (!space) {
+ isc_throw(InvalidOptionSpace,
+ "provided option space object is NULL.");
+ }
+ OptionSpaceCollection::iterator it = spaces6_.find(space->getName());
+ if (it != spaces6_.end()) {
+ isc_throw(InvalidOptionSpace, "option space " << space->getName()
+ << " already added.");
+ }
+ spaces6_.insert(std::pair<std::string,
+ OptionSpacePtr>(space->getName(), space));
+}
+
+void
+CfgMgr::addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space) {
+ // @todo we need better validation of the provided option space name here.
+ // This will be implemented when #2313 is merged.
+ if (option_space.empty()) {
+ isc_throw(BadValue, "option space name must not be empty");
+ } else if (!def) {
+ // Option definition must point to a valid object.
+ isc_throw(MalformedOptionDefinition, "option definition must not be NULL");
+
+ } else if (getOptionDef(option_space, def->getCode())) {
+ // Option definition must not be overriden.
+ isc_throw(DuplicateOptionDefinition, "option definition already added"
+ << " to option space " << option_space);
+
+ } else if ((option_space == "dhcp4" &&
+ LibDHCP::isStandardOption(Option::V4, def->getCode())) ||
+ (option_space == "dhcp6" &&
+ LibDHCP::isStandardOption(Option::V6, def->getCode()))) {
+ // We must not override standard (assigned) option. The standard options
+ // belong to dhcp4 or dhcp6 option space.
+ isc_throw(BadValue, "unable to override definition of option '"
+ << def->getCode() << "' in standard option space '"
+ << option_space << "'.");
+
+ }
+ // Actually add a new item.
+ option_def_spaces_.addItem(def, option_space);
+}
+
+OptionDefContainerPtr
+CfgMgr::getOptionDefs(const std::string& option_space) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ return (option_def_spaces_.getItems(option_space));
+}
+
+OptionDefinitionPtr
+CfgMgr::getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const {
+ // @todo Validate the option space once the #2313 is implemented.
+
+ // Get a reference to option definitions for a particular option space.
+ OptionDefContainerPtr defs = getOptionDefs(option_space);
+ // If there are no matching option definitions then return the empty pointer.
+ if (!defs || defs->empty()) {
+ return (OptionDefinitionPtr());
+ }
+ // If there are some option definitions for a particular option space
+ // use an option code to get the one we want.
+ const OptionDefContainerTypeIndex& idx = defs->get<1>();
+ const OptionDefContainerTypeRange& range = idx.equal_range(option_code);
+ // If there is no definition that matches option code, return empty pointer.
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDefinitionPtr());
+ }
+ // If there is more than one definition matching an option code, return
+ // the first one. This should not happen because we check for duplicates
+ // when addOptionDef is called.
+ return (*range.first);
+}
+
+Subnet6Ptr
+CfgMgr::getSubnet6(const std::string& iface) {
+
+ if (!iface.length()) {
+ return (Subnet6Ptr());
+ }
+
+ // If there is more than one, we need to choose the proper one
+ for (Subnet6Collection::iterator subnet = subnets6_.begin();
+ subnet != subnets6_.end(); ++subnet) {
+ if (iface == (*subnet)->getIface()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6_IFACE)
+ .arg((*subnet)->toText()).arg(iface);
+ return (*subnet);
+ }
+ }
+ return (Subnet6Ptr());
+}
+
Subnet6Ptr
CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
@@ -42,18 +154,27 @@ CfgMgr::getSubnet6(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if ((subnets6_.size() == 1) && hint.getAddress().to_v6().is_link_local()) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET6)
+ .arg(subnets6_[0]->toText()).arg(hint.toText());
return (subnets6_[0]);
}
// If there is more than one, we need to choose the proper one
for (Subnet6Collection::iterator subnet = subnets6_.begin();
subnet != subnets6_.end(); ++subnet) {
+
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET6)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET6)
+ .arg(hint.toText());
return (Subnet6Ptr());
}
@@ -65,6 +186,8 @@ Subnet6Ptr CfgMgr::getSubnet6(OptionPtr /*interfaceId*/) {
void CfgMgr::addSubnet6(const Subnet6Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET6)
+ .arg(subnet->toText());
subnets6_.push_back(subnet);
}
@@ -80,6 +203,9 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
// The server does not need to have a global address (using just link-local
// is ok for DHCPv6 server) from the pool it serves.
if (subnets4_.size() == 1) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_ONLY_SUBNET4)
+ .arg(subnets4_[0]->toText()).arg(hint.toText());
return (subnets4_[0]);
}
@@ -87,29 +213,51 @@ CfgMgr::getSubnet4(const isc::asiolink::IOAddress& hint) {
for (Subnet4Collection::iterator subnet = subnets4_.begin();
subnet != subnets4_.end(); ++subnet) {
if ((*subnet)->inRange(hint)) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE,
+ DHCPSRV_CFGMGR_SUBNET4)
+ .arg((*subnet)->toText()).arg(hint.toText());
return (*subnet);
}
}
// sorry, we don't support that subnet
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_NO_SUBNET4)
+ .arg(hint.toText());
return (Subnet4Ptr());
}
void CfgMgr::addSubnet4(const Subnet4Ptr& subnet) {
/// @todo: Check that this new subnet does not cross boundaries of any
/// other already defined subnet.
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_ADD_SUBNET4)
+ .arg(subnet->toText());
subnets4_.push_back(subnet);
}
+void CfgMgr::deleteOptionDefs() {
+ option_def_spaces_.clearItems();
+}
+
void CfgMgr::deleteSubnets4() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET4);
subnets4_.clear();
}
void CfgMgr::deleteSubnets6() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_CFGMGR_DELETE_SUBNET6);
subnets6_.clear();
}
-CfgMgr::CfgMgr() {
+std::string CfgMgr::getDataDir() {
+ return (datadir_);
+}
+
+
+CfgMgr::CfgMgr()
+ :datadir_(DHCP_DATA_DIR) {
+ // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
+ // Note: the definition of DHCP_DATA_DIR needs to include quotation marks
+ // See AM_CPPFLAGS definition in Makefile.am
}
CfgMgr::~CfgMgr() {
diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h
index ac1b3f5..0e56869 100644
--- a/src/lib/dhcpsrv/cfgmgr.h
+++ b/src/lib/dhcpsrv/cfgmgr.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -17,6 +17,9 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcp/option_definition.h>
+#include <dhcpsrv/option_space.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/subnet.h>
#include <util/buffer.h>
@@ -77,6 +80,71 @@ public:
/// accessing it.
static CfgMgr& instance();
+ /// @brief Add new option definition.
+ ///
+ /// @param def option definition to be added.
+ /// @param option_space name of the option space to add definition to.
+ ///
+ /// @throw isc::dhcp::DuplicateOptionDefinition when the particular
+ /// option definition already exists.
+ /// @throw isc::dhcp::MalformedOptionDefinition when the pointer to
+ /// an option definition is NULL.
+ /// @throw isc::BadValue when the option space name is empty or
+ /// when trying to override the standard option (in dhcp4 or dhcp6
+ /// option space).
+ void addOptionDef(const OptionDefinitionPtr& def,
+ const std::string& option_space);
+
+ /// @brief Return option definitions for particular option space.
+ ///
+ /// @param option_space option space.
+ ///
+ /// @return pointer to the collection of option definitions for
+ /// the particular option space. The option collection is empty
+ /// if no option exists for the option space specified.
+ OptionDefContainerPtr
+ getOptionDefs(const std::string& option_space) const;
+
+ /// @brief Return option definition for a particular option space and code.
+ ///
+ /// @param option_space option space.
+ /// @param option_code option code.
+ ///
+ /// @return an option definition or NULL pointer if option definition
+ /// has not been found.
+ OptionDefinitionPtr getOptionDef(const std::string& option_space,
+ const uint16_t option_code) const;
+
+ /// @brief Adds new DHCPv4 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace4(const OptionSpacePtr& space);
+
+ /// @brief Adds new DHCPv6 option space to the collection.
+ ///
+ /// @param space option space to be added.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace invalid option space
+ /// has been specified.
+ void addOptionSpace6(const OptionSpacePtr& space);
+
+ /// @brief Return option spaces for DHCPv4.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces4() const {
+ return (spaces4_);
+ }
+
+ /// @brief Return option spaces for DHCPv6.
+ ///
+ /// @return A collection of option spaces.
+ const OptionSpaceCollection& getOptionSpaces6() const {
+ return (spaces6_);
+ }
+
/// @brief get IPv6 subnet by address
///
/// Finds a matching subnet, based on an address. This can be used
@@ -86,19 +154,37 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object (or NULL if no suitable match was fount)
Subnet6Ptr getSubnet6(const isc::asiolink::IOAddress& hint);
+ /// @brief get IPv6 subnet by interface name
+ ///
+ /// Finds a matching local subnet, based on interface name. This
+ /// is used for selecting subnets that were explicitly marked by the
+ /// user as reachable over specified network interface.
+ /// @param iface_name interface name
+ /// @return a subnet object (or NULL if no suitable match was fount)
+ Subnet6Ptr getSubnet6(const std::string& iface_name);
+
/// @brief get IPv6 subnet by interface-id
///
/// Another possibility to find a subnet is based on interface-id.
///
/// @param interface_id content of interface-id option returned by a relay
+ ///
+ /// @return a subnet object
/// @todo This method is not currently supported.
Subnet6Ptr getSubnet6(OptionPtr interface_id);
/// @brief adds an IPv6 subnet
+ ///
+ /// @param subnet new subnet to be added.
void addSubnet6(const Subnet6Ptr& subnet);
+ /// @brief Delete all option definitions.
+ void deleteOptionDefs();
+
/// @todo: Add subnet6 removal routines. Currently it is not possible
/// to remove subnets. The only case where subnet6 removal would be
/// needed is a dynamic server reconfiguration - a use case that is not
@@ -125,6 +211,8 @@ public:
/// (for directly connected clients)
///
/// @param hint an address that belongs to a searched subnet
+ ///
+ /// @return a subnet object
Subnet4Ptr getSubnet4(const isc::asiolink::IOAddress& hint);
/// @brief adds a subnet4
@@ -141,6 +229,15 @@ public:
/// 192.0.2.0/23 and 192.0.2.0/24 the same subnet or is it something
/// completely new?
void deleteSubnets4();
+
+
+ /// @brief returns path do the data directory
+ ///
+ /// This method returns a path to writeable directory that DHCP servers
+ /// can store data in.
+ /// @return data directory
+ std::string getDataDir();
+
protected:
/// @brief Protected constructor.
@@ -169,6 +266,24 @@ protected:
/// pattern will use calling inRange() method on each subnet until
/// a match is found.
Subnet4Collection subnets4_;
+
+private:
+
+ /// @brief A collection of option definitions.
+ ///
+ /// A collection of option definitions that can be accessed
+ /// using option space name they belong to.
+ OptionSpaceContainer<OptionDefContainer,
+ OptionDefinitionPtr> option_def_spaces_;
+
+ /// @brief Container for defined DHCPv6 option spaces.
+ OptionSpaceCollection spaces6_;
+
+ /// @brief Container for defined DHCPv4 option spaces.
+ OptionSpaceCollection spaces4_;
+
+ /// @brief directory where data files (e.g. server-id) are stored
+ std::string datadir_;
};
} // namespace isc::dhcp
diff --git a/src/lib/dhcpsrv/dhcp_config_parser.h b/src/lib/dhcpsrv/dhcp_config_parser.h
new file mode 100644
index 0000000..d3d05f6
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcp_config_parser.h
@@ -0,0 +1,156 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCP_CONFIG_PARSER_H
+#define DHCP_CONFIG_PARSER_H
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if an error occurs while configuring
+/// DHCP server.
+class DhcpConfigError : public isc::Exception {
+public:
+
+ /// @brief constructor
+ ///
+ /// @param file name of the file, where exception occurred
+ /// @param line line of the file, where exception occurred
+ /// @param what text description of the issue that caused exception
+ DhcpConfigError(const char* file, size_t line, const char* what)
+ : isc::Exception(file, line, what) {}
+};
+
+/// @brief Forward declaration to DhcpConfigParser class.
+///
+/// It is only needed here to define types that are
+/// based on this class before the class definition.
+class DhcpConfigParser;
+
+/// @brief a pointer to configuration parser
+typedef boost::shared_ptr<DhcpConfigParser> ParserPtr;
+
+/// @brief Collection of parsers.
+///
+/// This container is used to store pointer to parsers for a given scope.
+typedef std::vector<ParserPtr> ParserCollection;
+
+/// @brief Base abstract class for all DHCP parsers
+///
+/// Each instance of a class derived from this class parses one specific config
+/// element. Sometimes elements are simple (e.g. a string) and sometimes quite
+/// complex (e.g. a subnet). In such case, it is likely that a parser will
+/// spawn child parsers to parse child elements in the configuration.
+class DhcpConfigParser {
+ ///
+ /// @name Constructors and Destructor
+ ///
+ /// Note: The copy constructor and the assignment operator are
+ /// intentionally defined as private to make it explicit that this is a
+ /// pure base class.
+ //@{
+private:
+
+ // Private construtor and assignment operator assures that nobody
+ // will be able to copy or assign a parser. There are no defined
+ // bodies for them.
+ DhcpConfigParser(const DhcpConfigParser& source);
+ DhcpConfigParser& operator=(const DhcpConfigParser& source);
+protected:
+ /// @brief The default constructor.
+ ///
+ /// This is intentionally defined as @c protected as this base class should
+ /// never be instantiated (except as part of a derived class).
+ DhcpConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~DhcpConfigParser() {}
+ //@}
+
+ /// @brief Prepare configuration value.
+ ///
+ /// This method parses the "value part" of the configuration identifier
+ /// that corresponds to this derived class and prepares a new value to
+ /// apply to the server.
+ ///
+ /// This method must validate the given value both in terms of syntax
+ /// and semantics of the configuration, so that the server will be
+ /// validly configured at the time of @c commit(). Note: the given
+ /// configuration value is normally syntactically validated, but the
+ /// @c build() implementation must also expect invalid input. If it
+ /// detects an error it may throw an exception of a derived class
+ /// of @c isc::Exception.
+ ///
+ /// Preparing a configuration value will often require resource
+ /// allocation. If it fails, it may throw a corresponding standard
+ /// exception.
+ ///
+ /// This method is not expected to be called more than once in the
+ /// life of the object. Although multiple calls are not prohibited
+ /// by the interface, the behavior is undefined.
+ ///
+ /// @param config_value The configuration value for the identifier
+ /// corresponding to the derived class.
+ virtual void build(isc::data::ConstElementPtr config_value) = 0;
+
+ /// @brief Apply the prepared configuration value to the server.
+ ///
+ /// This method is expected to be exception free, and, as a consequence,
+ /// it should normally not involve resource allocation.
+ /// Typically it would simply perform exception free assignment or swap
+ /// operation on the value prepared in @c build().
+ /// In some cases, however, it may be very difficult to meet this
+ /// condition in a realistic way, while the failure case should really
+ /// be very rare. In such a case it may throw, and, if the parser is
+ /// called via @c configureDhcp4Server(), the caller will convert the
+ /// exception as a fatal error.
+ ///
+ /// This method is expected to be called after @c build(), and only once.
+ /// The result is undefined otherwise.
+ virtual void commit() = 0;
+
+protected:
+
+ /// @brief Return the parsed entry from the provided storage.
+ ///
+ /// This method returns the parsed entry from the provided
+ /// storage. If the entry is not found, then exception is
+ /// thrown.
+ ///
+ /// @param param_id name of the configuration entry.
+ /// @param storage storage where the entry should be searched.
+ /// @tparam ReturnType type of the returned value.
+ /// @tparam StorageType type of the storage.
+ ///
+ /// @throw DhcpConfigError if the entry has not been found
+ /// in the storage.
+ template<typename ReturnType, typename StorageType>
+ static ReturnType getParam(const std::string& param_id,
+ const StorageType& storage) {
+ typename StorageType::const_iterator param = storage.find(param_id);
+ if (param == storage.end()) {
+ isc_throw(DhcpConfigError, "missing parameter '"
+ << param_id << "'");
+ }
+ ReturnType value = param->second;
+ return (value);
+ }
+
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // DHCP_CONFIG_PARSER_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.cc b/src/lib/dhcpsrv/dhcpsrv_log.cc
new file mode 100644
index 0000000..6cbfb0d
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+/// Defines the logger used by the NSAS
+
+#include "dhcpsrv/dhcpsrv_log.h"
+
+namespace isc {
+namespace dhcp {
+
+isc::log::Logger dhcpsrv_logger("dhcpsrv");
+
+} // namespace dhcp
+} // namespace isc
+
diff --git a/src/lib/dhcpsrv/dhcpsrv_log.h b/src/lib/dhcpsrv/dhcpsrv_log.h
new file mode 100644
index 0000000..9b6350a
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_log.h
@@ -0,0 +1,66 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_LOG_H
+#define DHCPSRV_LOG_H
+
+#include <dhcpsrv/dhcpsrv_messages.h>
+#include <log/macros.h>
+
+namespace isc {
+namespace dhcp {
+
+///@{
+/// \brief DHCP server library logging levels
+///
+/// Defines the levels used to output debug messages in the DHCP server
+/// library. Note that higher numbers equate to more verbose (and detailed)
+/// output.
+
+/// @brief Traces normal operations
+///
+/// E.g. sending a query to the database etc.
+const int DHCPSRV_DBG_TRACE = DBGLVL_TRACE_BASIC;
+
+/// @brief Records the results of the lookups
+///
+/// Using the example of tracing queries from the backend database, this will
+/// just record the summary results.
+const int DHCPSRV_DBG_RESULTS = DBGLVL_TRACE_BASIC_DATA;
+
+/// @brief Additional information
+///
+/// Record detailed tracing. This is generally reserved for tracing access to
+/// the lease database.
+const int DHCPSRV_DBG_TRACE_DETAIL = DBGLVL_TRACE_DETAIL;
+
+/// @brief Additional information
+///
+/// Record detailed (and verbose) data on the server.
+const int DHCPSRV_DBG_TRACE_DETAIL_DATA = DBGLVL_TRACE_DETAIL_DATA;
+
+///@}
+
+
+/// \brief DHCP server library Logger
+///
+/// Define the logger used to log messages. We could define it in multiple
+/// modules, but defining in a single module and linking to it saves time and
+/// space.
+extern isc::log::Logger dhcpsrv_logger;
+
+} // namespace dhcp
+} // namespace isc
+
+#endif // DHCPSRV_LOG_H
diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes
new file mode 100644
index 0000000..8a7610b
--- /dev/null
+++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes
@@ -0,0 +1,237 @@
+# Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+$NAMESPACE isc::dhcp
+
+% DHCPSRV_CFGMGR_ADD_SUBNET4 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv4 subnet to its database.
+
+% DHCPSRV_CFGMGR_ADD_SUBNET6 adding subnet %1
+A debug message reported when the DHCP configuration manager is adding the
+specified IPv6 subnet to its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET4 deleting all IPv4 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv4
+subnets in its database.
+
+% DHCPSRV_CFGMGR_DELETE_SUBNET6 deleting all IPv6 subnets
+A debug message noting that the DHCP configuration manager has deleted all IPv6
+subnets in its database.
+
+% DHCPSRV_CFGMGR_NO_SUBNET4 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv4 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_NO_SUBNET6 no suitable subnet is defined for address hint %1
+This debug message is output when the DHCP configuration manager has received
+a request for an IPv6 subnet for the specified address, but no such
+subnet exists.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_ONLY_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+because it is the only subnet defined.
+
+% DHCPSRV_CFGMGR_SUBNET4 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv4 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6 retrieved subnet %1 for address hint %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet when given the address hint specified
+as the address is within the subnet.
+
+% DHCPSRV_CFGMGR_SUBNET6_IFACE selected subnet %1 for packet received over interface %2
+This is a debug message reporting that the DHCP configuration manager has
+returned the specified IPv6 subnet for a packet received over given interface.
+This particular subnet was selected, because it was specified as being directly
+reachable over given interface. (see 'interface' parameter in subnet6 definition).
+
+% DHCPSRV_INVALID_ACCESS invalid database access string: %1
+This is logged when an attempt has been made to parse a database access string
+and the attempt ended in error. The access string in question - which
+should be of the form 'keyword=value keyword=value...' is included in
+the message.
+
+% DHCPSRV_MEMFILE_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the memory file backend database.
+
+% DHCPSRV_MEMFILE_COMMIT commiting to memory file database
+The code has issued a commit call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_DB opening memory file lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a memory file lease database. The parameters of
+the connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MEMFILE_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease
+for the specified address from the memory file database for the specified
+address.
+
+% DHCPSRV_MEMFILE_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+client identification.
+
+% DHCPSRV_MEMFILE_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set of
+IPv4 leases from the memory file database for a client with the specified
+hardware address.
+
+% DHCPSRV_MEMFILE_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the memory file database for a client with the specified
+IAID (Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the memory file database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MEMFILE_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and client ID.
+
+% DHCPSRV_MEMFILE_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the memory file database for a client with the specified
+subnet ID and hardware address.
+
+% DHCPSRV_MEMFILE_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the memory file database.
+
+% DHCPSRV_MEMFILE_ROLLBACK rolling back memory file database
+The code has issued a rollback call. For the memory file database, this is
+a no-op.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MEMFILE_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the memory file database for the specified address.
+
+% DHCPSRV_MYSQL_ADD_ADDR4 adding IPv4 lease with address %1
+A debug message issued when the server is about to add an IPv4 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_ADD_ADDR6 adding IPv6 lease with address %1
+A debug message issued when the server is about to add an IPv6 lease
+with the specified address to the MySQL backend database.
+
+% DHCPSRV_MYSQL_COMMIT commiting to MySQl database
+The code has issued a commit call. All outstanding transactions will be
+committed to the database. Note that depending on the MySQL settings,
+the commital may not include a write to disk.
+
+% DHCPSRV_MYSQL_DB opening MySQL lease database: %1
+This informational message is logged when a DHCP server (either V4 or
+V6) is about to open a MySQL lease database. The parameters of the
+connection including database name and username needed to access it
+(but not the password if any) are logged.
+
+% DHCPSRV_MYSQL_DELETE_ADDR deleting lease for address %1
+A debug message issued when the server is attempting to delete a lease for
+the specified address from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR4 obtaining IPv4 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_ADDR6 obtaining IPv6 lease for address %1
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_GET_CLIENTID obtaining IPv4 leases for client ID %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+client identification.
+
+% DHCPSRV_MYSQL_GET_HWADDR obtaining IPv4 leases for hardware address %1
+A debug message issued when the server is attempting to obtain a set
+of IPv4 leases from the MySQL database for a client with the specified
+hardware address.
+
+% DHCPSRV_MYSQL_GET_IAID_DUID obtaining IPv4 leases for IAID %1 and DUID %2
+A debug message issued when the server is attempting to obtain a set of
+IPv6 lease from the MySQL database for a client with the specified IAID
+(Identity Association ID) and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_IAID_SUBID_DUID obtaining IPv4 leases for IAID %1, Subnet ID %2 and DUID %3
+A debug message issued when the server is attempting to obtain an IPv6
+lease from the MySQL database for a client with the specified IAID
+(Identity Association ID), Subnet ID and DUID (DHCP Unique Identifier).
+
+% DHCPSRV_MYSQL_GET_SUBID_CLIENTID obtaining IPv4 lease for subnet ID %1 and client ID %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and client ID.
+
+% DHCPSRV_MYSQL_GET_SUBID_HWADDR obtaining IPv4 lease for subnet ID %1 and hardware address %2
+A debug message issued when the server is attempting to obtain an IPv4
+lease from the MySQL database for a client with the specified subnet ID
+and hardware address.
+
+% DHCPSRV_MYSQL_GET_VERSION obtaining schema version information
+A debug message issued when the server is about to obtain schema version
+information from the MySQL database.
+
+% DHCPSRV_MYSQL_ROLLBACK rolling back MySQL database
+The code has issued a rollback call. All outstanding transaction will
+be rolled back and not committed to the database.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR4 updating IPv4 lease for address %1
+A debug message issued when the server is attempting to update IPv4
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_MYSQL_UPDATE_ADDR6 updating IPv6 lease for address %1
+A debug message issued when the server is attempting to update IPv6
+lease from the MySQL database for the specified address.
+
+% DHCPSRV_NOTYPE_DB no 'type' keyword to determine database backend: %1
+This is an error message, logged when an attempt has been made to access
+a database backend, but where no 'type' keyword has been included in
+the access string. The access string (less any passwords) is included
+in the message.
+
+% DHCPSRV_UNKNOWN_DB unknown database type: %1
+The database access string specified a database type (given in the
+message) that is unknown to the software. This is a configuration error.
diff --git a/src/lib/dhcpsrv/lease_mgr.cc b/src/lib/dhcpsrv/lease_mgr.cc
index b3a605d..6608b14 100644
--- a/src/lib/dhcpsrv/lease_mgr.cc
+++ b/src/lib/dhcpsrv/lease_mgr.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -32,13 +32,18 @@ using namespace std;
namespace isc {
namespace dhcp {
+Lease::Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt)
+ :addr_(addr), t1_(t1), t2_(t2), valid_lft_(valid_lft), cltt_(cltt),
+ subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false) {
+}
+
Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
DuidPtr duid, uint32_t iaid, uint32_t preferred, uint32_t valid,
uint32_t t1, uint32_t t2, SubnetID subnet_id, uint8_t prefixlen)
- : addr_(addr), type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
- preferred_lft_(preferred), valid_lft_(valid), t1_(t1), t2_(t2),
- subnet_id_(subnet_id), fixed_(false), fqdn_fwd_(false),
- fqdn_rev_(false) {
+ : Lease(addr, t1, t2, valid, subnet_id, 0/*cltt*/),
+ type_(type), prefixlen_(prefixlen), iaid_(iaid), duid_(duid),
+ preferred_lft_(preferred) {
if (!duid) {
isc_throw(InvalidOperation, "DUID must be specified for a lease");
}
@@ -46,7 +51,7 @@ Lease6::Lease6(LeaseType type, const isc::asiolink::IOAddress& addr,
cltt_ = time(NULL);
}
-bool Lease6::expired() const {
+bool Lease::expired() const {
// Let's use int64 to avoid problems with negative/large uint32 values
int64_t expire_time = cltt_ + valid_lft_;
@@ -63,7 +68,7 @@ std::string LeaseMgr::getParameter(const std::string& name) const {
}
std::string
-Lease6::toText() {
+Lease6::toText() const {
ostringstream stream;
stream << "Type: " << static_cast<int>(type_) << " (";
@@ -91,6 +96,21 @@ Lease6::toText() {
return (stream.str());
}
+std::string
+Lease4::toText() const {
+ ostringstream stream;
+
+ stream << "Address: " << addr_.toText() << "\n"
+ << "Valid life: " << valid_lft_ << "\n"
+ << "T1: " << t1_ << "\n"
+ << "T2: " << t2_ << "\n"
+ << "Cltt: " << cltt_ << "\n"
+ << "Subnet ID: " << subnet_id_ << "\n";
+
+ return (stream.str());
+}
+
+
bool
Lease4::operator==(const Lease4& other) const {
return (
diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h
index 3fe11e6..f781576 100644
--- a/src/lib/dhcpsrv/lease_mgr.h
+++ b/src/lib/dhcpsrv/lease_mgr.h
@@ -18,6 +18,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
#include <dhcp/option.h>
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/subnet.h>
#include <exceptions/exceptions.h>
@@ -25,6 +26,7 @@
#include <boost/shared_ptr.hpp>
#include <fstream>
+#include <iostream>
#include <map>
#include <string>
#include <utility>
@@ -61,8 +63,6 @@
/// Nevertheless, we hope to have failover protocol eventually implemented in
/// the Kea.
-#include <iostream>
-
namespace isc {
namespace dhcp {
@@ -108,38 +108,21 @@ public:
isc::Exception(file, line, what) {}
};
-/// @brief Structure that holds a lease for IPv4 address
+/// @brief a common structure for IPv4 and IPv6 leases
///
-/// For performance reasons it is a simple structure, not a class. If we chose
-/// make it a class, all fields would have to made private and getters/setters
-/// would be required. As this is a critical part of the code that will be used
-/// extensively, direct access is warranted.
-struct Lease4 {
- /// @brief Maximum size of a hardware address
- static const size_t HWADDR_MAX = 20;
-
+/// This structure holds all information that is common between IPv4 and IPv6
+/// leases.
+struct Lease {
- /// IPv4 address
- isc::asiolink::IOAddress addr_;
-
- /// @brief Address extension
- ///
- /// It is envisaged that in some cases IPv4 address will be accompanied
- /// with some additional data. One example of such use are Address + Port
- /// solutions (or Port-restricted Addresses), where several clients may get
- /// the same address, but different port ranges. This feature is not
- /// expected to be widely used. Under normal circumstances, the value
- /// should be 0.
- uint32_t ext_;
+ Lease(const isc::asiolink::IOAddress& addr, uint32_t t1, uint32_t t2,
+ uint32_t valid_lft, SubnetID subnet_id, time_t cltt);
- /// @brief Hardware address
- std::vector<uint8_t> hwaddr_;
+ virtual ~Lease() {}
- /// @brief Client identifier
+ /// @brief IPv4 ot IPv6 address
///
- /// @todo Should this be a pointer to a client ID or the ID itself?
- /// Compare with the DUID in the Lease6 structure.
- ClientIdPtr client_id_;
+ /// IPv4, IPv6 address or, in the case of a prefix delegation, the prefix.
+ isc::asiolink::IOAddress addr_;
/// @brief Renewal timer
///
@@ -201,6 +184,46 @@ struct Lease4 {
/// system administrator.
std::string comments_;
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const = 0;
+
+ /// @brief returns true if the lease is expired
+ /// @return true if the lease is expired
+ bool expired() const;
+
+};
+
+/// @brief Structure that holds a lease for IPv4 address
+///
+/// For performance reasons it is a simple structure, not a class. If we chose
+/// make it a class, all fields would have to made private and getters/setters
+/// would be required. As this is a critical part of the code that will be used
+/// extensively, direct access is warranted.
+struct Lease4 : public Lease {
+ /// @brief Maximum size of a hardware address
+ static const size_t HWADDR_MAX = 20;
+
+ /// @brief Address extension
+ ///
+ /// It is envisaged that in some cases IPv4 address will be accompanied
+ /// with some additional data. One example of such use are Address + Port
+ /// solutions (or Port-restricted Addresses), where several clients may get
+ /// the same address, but different port ranges. This feature is not
+ /// expected to be widely used. Under normal circumstances, the value
+ /// should be 0.
+ uint32_t ext_;
+
+ /// @brief Hardware address
+ std::vector<uint8_t> hwaddr_;
+
+ /// @brief Client identifier
+ ///
+ /// @todo Should this be a pointer to a client ID or the ID itself?
+ /// Compare with the DUID in the Lease6 structure.
+ ClientIdPtr client_id_;
+
/// @brief Constructor
///
/// @param addr IPv4 address as unsigned 32-bit integer in network byte
@@ -212,21 +235,19 @@ struct Lease4 {
/// @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,
+ Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len,
const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft,
- 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_()
- {}
+ uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id)
+ : Lease(addr, t1, t2, valid_lft, subnet_id, cltt),
+ ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len),
+ client_id_(new ClientId(clientid, clientid_len)) {
+ }
- /// @brief Default Constructor
+ /// @brief Default constructor
///
/// Initialize fields that don't have a default constructor.
- Lease4() : addr_(0), fixed_(false), fqdn_fwd_(false), fqdn_rev_(false)
- {}
+ Lease4() : Lease(0, 0, 0, 0, 0, 0) {
+ }
/// @brief Compare two leases for equality
///
@@ -240,6 +261,11 @@ struct Lease4 {
return (!operator==(other));
}
+ /// @brief Convert lease to printable form
+ ///
+ /// @return Textual represenation of lease data
+ virtual std::string toText() const;
+
/// @todo: Add DHCPv4 failover related fields here
};
@@ -257,7 +283,7 @@ typedef std::vector<Lease4Ptr> Lease4Collection;
/// make it a class, all fields would have to made private and getters/setters
/// would be required. As this is a critical part of the code that will be used
/// extensively, direct access is warranted.
-struct Lease6 {
+struct Lease6 : public Lease {
/// @brief Type of lease contents
typedef enum {
@@ -266,11 +292,6 @@ struct Lease6 {
LEASE_IA_PD /// the lease contains IPv6 prefix (for prefix delegation)
} LeaseType;
- /// @brief IPv6 address
- ///
- /// IPv6 address or, in the case of a prefix delegation, the prefix.
- isc::asiolink::IOAddress addr_;
-
/// @brief Lease type
///
/// One of normal address, temporary address, or prefix.
@@ -297,69 +318,6 @@ struct Lease6 {
/// assigned or renewed (cltt), expressed in seconds.
uint32_t preferred_lft_;
- /// @brief valid lifetime
- ///
- /// This parameter specifies the valid lifetime since the lease waa
- /// assigned/renewed (cltt), expressed in seconds.
- uint32_t valid_lft_;
-
- /// @brief T1 timer
- ///
- /// Specifies renewal time. Although technically it is a property of the
- /// IA container and not the address itself, since our data model does not
- /// define a separate IA entity, we are keeping it in the lease. In the
- /// case of multiple addresses/prefixes for the same IA, each must have
- /// consistent T1 and T2 values. This is specified in seconds since cltt.
- /// The value will also be useful for failover to calculate the next
- /// expected client transmission time.
- uint32_t t1_;
-
- /// @brief T2 timer
- ///
- /// Specifies rebinding time. Although technically it is a property of the
- /// IA container and not the address itself, since our data model does not
- /// define a separate IA entity, we are keeping it in the lease. In the
- /// case of multiple addresses/prefixes for the same IA, each must have
- /// consistent T1 and T2 values. This is specified in seconds since cltt.
- uint32_t t2_;
-
- /// @brief Client last transmission time
- ///
- /// Specifies a timestamp giving the time when the last transmission from a
- /// client was received.
- time_t cltt_;
-
- /// @brief Subnet identifier
- ///
- /// Specifies the identification of the subnet to which the lease belongs.
- SubnetID subnet_id_;
-
- /// @brief Fixed lease?
- ///
- /// Fixed leases are kept after they are released/expired.
- bool fixed_;
-
- /// @brief Client hostname
- ///
- /// This field may be empty
- std::string hostname_;
-
- /// @brief Forward zone updated?
- ///
- /// Set true if the DNS AAAA record for this lease has been updated.
- bool fqdn_fwd_;
-
- /// @brief Reverse zone updated?
- ///
- /// Set true if the DNS PTR record for this lease has been updated.
- bool fqdn_rev_;
-
- /// @brief Lease comments
- ///
- /// Currently not used. It may be used for keeping comments made by the
- /// system administrator.
- std::string comments_;
-
/// @todo: Add DHCPv6 failover related fields here
/// @brief Constructor
@@ -370,18 +328,9 @@ struct Lease6 {
/// @brief Constructor
///
/// Initialize fields that don't have a default constructor.
- 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();
-
- /// @brief returns true if the lease is expired
- /// @return true if the lease is expired
- bool expired() const;
+ Lease6() : Lease(isc::asiolink::IOAddress("::"), 0, 0, 0, 0, 0),
+ type_(LEASE_IA_NA) {
+ }
/// @brief Compare two leases for equality
///
@@ -394,6 +343,11 @@ struct Lease6 {
bool operator!=(const Lease6& other) const {
return (!operator==(other));
}
+
+ /// @brief Convert Lease to Printable Form
+ ///
+ /// @return String form of the lease
+ virtual std::string toText() const;
};
/// @brief Pointer to a Lease6 structure.
@@ -417,9 +371,6 @@ typedef std::vector<Lease6Ptr> Lease6Collection;
/// see the documentation of those classes for details.
class LeaseMgr {
public:
- /// Client hardware address
- typedef std::vector<uint8_t> HWAddr;
-
/// Database configuration parameter map
typedef std::map<std::string, std::string> ParameterMap;
@@ -474,7 +425,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const = 0;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const = 0;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -486,7 +437,7 @@ public:
/// @param subnet_id identifier of the subnet that lease must belong to
///
/// @return a pointer to the lease (or NULL if a lease is not found)
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const = 0;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.cc b/src/lib/dhcpsrv/lease_mgr_factory.cc
index 47c53ed..9fd276d 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.cc
+++ b/src/lib/dhcpsrv/lease_mgr_factory.cc
@@ -14,6 +14,7 @@
#include "config.h"
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/memfile_lease_mgr.h>
#ifdef HAVE_MYSQL
@@ -43,17 +44,17 @@ LeaseMgrFactory::getLeaseMgrPtr() {
}
LeaseMgr::ParameterMap
-LeaseMgrFactory::parse(const std::string& dbconfig) {
+LeaseMgrFactory::parse(const std::string& dbaccess) {
LeaseMgr::ParameterMap mapped_tokens;
- if (!dbconfig.empty()) {
+ if (!dbaccess.empty()) {
vector<string> tokens;
// We need to pass a string to is_any_of, not just char*. Otherwise
// there are cryptic warnings on Debian6 running g++ 4.4 in
// /usr/include/c++/4.4/bits/stl_algo.h:2178 "array subscript is above
// array bounds"
- boost::split(tokens, dbconfig, boost::is_any_of( string("\t ") ));
+ boost::split(tokens, dbaccess, boost::is_any_of(string("\t ")));
BOOST_FOREACH(std::string token, tokens) {
size_t pos = token.find("=");
if (pos != string::npos) {
@@ -61,6 +62,7 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
string value = token.substr(pos + 1);
mapped_tokens.insert(make_pair(name, value));
} else {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_INVALID_ACCESS).arg(dbaccess);
isc_throw(InvalidParameter, "Cannot parse " << token
<< ", expected format is name=value");
}
@@ -70,31 +72,70 @@ LeaseMgrFactory::parse(const std::string& dbconfig) {
return (mapped_tokens);
}
+std::string
+LeaseMgrFactory::redactedAccessString(const LeaseMgr::ParameterMap& parameters) {
+ // Reconstruct the access string: start of with an empty string, then
+ // work through all the parameters in the original string and add them.
+ std::string access;
+ for (LeaseMgr::ParameterMap::const_iterator i = parameters.begin();
+ i != parameters.end(); ++i) {
+
+ // Separate second and subsequent tokens are preceded by a space.
+ if (!access.empty()) {
+ access += " ";
+ }
+
+ // Append name of parameter...
+ access += i->first;
+ access += "=";
+
+ // ... and the value, except in the case of the password, where a
+ // redacted value is appended.
+ if (i->first == std::string("password")) {
+ access += "*****";
+ } else {
+ access += i->second;
+ }
+ }
+
+ return (access);
+}
+
void
-LeaseMgrFactory::create(const std::string& dbconfig) {
+LeaseMgrFactory::create(const std::string& dbaccess) {
const std::string type = "type";
+ // Parse the access string and create a redacted string for logging.
+ LeaseMgr::ParameterMap parameters = parse(dbaccess);
+ std::string redacted = redactedAccessString(parameters);
+
// Is "type" present?
- LeaseMgr::ParameterMap parameters = parse(dbconfig);
if (parameters.find(type) == parameters.end()) {
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_NOTYPE_DB).arg(dbaccess);
isc_throw(InvalidParameter, "Database configuration parameters do not "
"contain the 'type' keyword");
}
+
// Yes, check what it is.
#ifdef HAVE_MYSQL
if (parameters[type] == string("mysql")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MYSQL_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new MySqlLeaseMgr(parameters));
return;
}
#endif
if (parameters[type] == string("memfile")) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE, DHCPSRV_MEMFILE_DB)
+ .arg(redacted);
getLeaseMgrPtr().reset(new Memfile_LeaseMgr(parameters));
return;
}
// Get here on no match
- isc_throw(InvalidType, "Database configuration parameter 'type' does "
+ LOG_ERROR(dhcpsrv_logger, DHCPSRV_UNKNOWN_DB).arg(parameters[type]);
+ isc_throw(InvalidType, "Database access parameter 'type' does "
"not specify a supported database backend");
}
diff --git a/src/lib/dhcpsrv/lease_mgr_factory.h b/src/lib/dhcpsrv/lease_mgr_factory.h
index 338c941..9218cf5 100644
--- a/src/lib/dhcpsrv/lease_mgr_factory.h
+++ b/src/lib/dhcpsrv/lease_mgr_factory.h
@@ -66,21 +66,21 @@ public:
/// @note When called, the current lease manager is <b>always</b> destroyed
/// and a new one created - even if the parameters are the same.
///
- /// dbconfig is a generic way of passing parameters. Parameters are passed
+ /// dbaccess is a generic way of passing parameters. Parameters are passed
/// in the "name=value" format, separated by spaces. The data MUST include
/// a keyword/value pair of the form "type=dbtype" giving the database
/// type, e.q. "mysql" or "sqlite3".
///
- /// @param dbconfig Database configuration parameters. These are in
- /// the form of "keyword=value" pairs, separated by spaces. These
- /// are back-end specific, although must include the "type" keyword
- /// which gives the backend in use.
+ /// @param dbaccess Database access parameters. These are in the form of
+ /// "keyword=value" pairs, separated by spaces. They are backend-
+ /// -end specific, although must include the "type" keyword which
+ /// gives the backend in use.
///
- /// @throw isc::InvalidParameter dbconfig string does not contain the "type"
+ /// @throw isc::InvalidParameter dbaccess string does not contain the "type"
/// keyword.
- /// @throw isc::dhcp::InvalidType The "type" keyword in dbconfig does not
+ /// @throw isc::dhcp::InvalidType The "type" keyword in dbaccess does not
/// identify a supported backend.
- static void create(const std::string& dbconfig);
+ static void create(const std::string& dbaccess);
/// @brief Destroy lease manager
///
@@ -89,7 +89,7 @@ public:
/// lease manager is available.
static void destroy();
- /// @brief Return Current Lease Manager
+ /// @brief Return current lease manager
///
/// Returns an instance of the "current" lease manager. An exception
/// will be thrown if none is available.
@@ -98,15 +98,26 @@ public:
/// create() to create one before calling this method.
static LeaseMgr& instance();
- /// @brief Parse Database Parameters
+ /// @brief Parse database access string
///
/// Parses the string of "keyword=value" pairs and separates them
/// out into the map.
///
- /// @param dbconfig Database configuration string
+ /// @param dbaccess Database access string.
///
/// @return std::map<std::string, std::string> Map of keyword/value pairs.
- static LeaseMgr::ParameterMap parse(const std::string& dbconfig);
+ static LeaseMgr::ParameterMap parse(const std::string& dbaccess);
+
+ /// @brief Redact database access string
+ ///
+ /// Takes the database parameters and returns a database access string
+ /// passwords replaced by asterisks. This string is used in log messages.
+ ///
+ /// @param parameters Database access parameters (output of "parse").
+ ///
+ /// @return Redacted database access string.
+ static std::string redactedAccessString(
+ const LeaseMgr::ParameterMap& parameters);
private:
/// @brief Hold pointer to lease manager
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc
index 79cf1c7..34f21e9 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.cc
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc
@@ -12,7 +12,9 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/memfile_lease_mgr.h>
+#include <exceptions/exceptions.h>
#include <iostream>
@@ -27,11 +29,22 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters)
Memfile_LeaseMgr::~Memfile_LeaseMgr() {
}
-bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) {
- return (false);
+bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR4).arg(lease->addr_.toText());
+
+ if (getLease4(lease->addr_)) {
+ // there is a lease with specified address already
+ return (false);
+ }
+ storage4_.insert(lease);
+ return (true);
}
bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ADD_ADDR6).arg(lease->addr_.toText());
+
if (getLease6(lease->addr_)) {
// there is a lease with specified address already
return (false);
@@ -40,30 +53,71 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) {
return (true);
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const {
- return (Lease4Ptr());
+Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR4).arg(addr.toText());
+
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ return (Lease4Ptr());
+ } else {
+ return (*l);
+ }
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const {
- return (Lease4Collection());
+Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_HWADDR).arg(hwaddr.toText());
+
+ isc_throw(NotImplemented, "getLease4(HWaddr x) method not implemented yet");
}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_HWADDR).arg(subnet_id)
+ .arg(hwaddr.toText());
+
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( ((*l)->hwaddr_ == hwaddr.hwaddr_) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+
+ // not found
return (Lease4Ptr());
}
+Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_CLIENTID).arg(clientid.toText());
+ isc_throw(NotImplemented, "getLease4(ClientId) not implemented");
+}
-Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&,
- SubnetID) const {
+Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id,
+ SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_SUBID_CLIENTID).arg(subnet_id)
+ .arg(client_id.toText());
+ Lease4Storage::iterator l;
+ for (l = storage4_.begin(); l != storage4_.end(); ++l) {
+ if ( (*(*l)->client_id_ == client_id) &&
+ ((*l)->subnet_id_ == subnet_id)) {
+ return (*l);
+ }
+ }
+
+ // not found
return (Lease4Ptr());
}
-Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const {
- return (Lease4Collection());
-}
+Lease6Ptr Memfile_LeaseMgr::getLease6(
+ const isc::asiolink::IOAddress& addr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_ADDR6).arg(addr.toText());
-Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const {
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
return (Lease6Ptr());
@@ -72,12 +126,20 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) cons
}
}
-Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& , uint32_t ) const {
+Lease6Collection Memfile_LeaseMgr::getLease6(const DUID& duid,
+ uint32_t iaid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_DUID).arg(iaid).arg(duid.toText());
+
return (Lease6Collection());
}
Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
+
/// @todo: Slow, naive implementation. Write it using additional indexes
for (Lease6Storage::iterator l = storage6_.begin(); l != storage6_.end(); ++l) {
if ( (*((*l)->duid_) == duid) &&
@@ -89,20 +151,35 @@ Lease6Ptr Memfile_LeaseMgr::getLease6(const DUID& duid, uint32_t iaid,
return (Lease6Ptr());
}
-void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& ) {
+void Memfile_LeaseMgr::updateLease4(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR4).arg(lease->addr_.toText());
+
}
-void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) {
+void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_UPDATE_ADDR6).arg(lease->addr_.toText());
+
}
bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_DELETE_ADDR).arg(addr.toText());
if (addr.isV4()) {
- // V4 not implemented yet
- return (false);
+ // v4 lease
+ Lease4Storage::iterator l = storage4_.find(addr);
+ if (l == storage4_.end()) {
+ // No such lease
+ return (false);
+ } else {
+ storage4_.erase(l);
+ return (true);
+ }
} else {
- // V6 lease
+ // v6 lease
Lease6Storage::iterator l = storage6_.find(addr);
if (l == storage6_.end()) {
// No such lease
@@ -122,8 +199,11 @@ std::string Memfile_LeaseMgr::getDescription() const {
void
Memfile_LeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MEMFILE_COMMIT);
}
void
Memfile_LeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MEMFILE_ROLLBACK);
}
diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h
index 268b722..81e5fe3 100644
--- a/src/lib/dhcpsrv/memfile_lease_mgr.h
+++ b/src/lib/dhcpsrv/memfile_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MEMFILE_LEASE_MGR_H
#define MEMFILE_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/multi_index/indexed_by.hpp>
@@ -80,7 +81,7 @@ public:
/// @param hwaddr hardware address of the client
///
/// @return lease collection
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -225,12 +226,28 @@ protected:
// IPv6 address that are unique. That particular key is a member
// of the Lease6 structure, is of type IOAddress and can be accessed
// by doing &Lease6::addr_
- boost::multi_index::ordered_unique<
- boost::multi_index::member<Lease6, isc::asiolink::IOAddress, &Lease6::addr_>
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
>
>
> Lease6Storage; // Let the whole contraption be called Lease6Storage.
+ typedef boost::multi_index_container< // this is a multi-index container...
+ Lease4Ptr, // it will hold shared_ptr to leases6
+ boost::multi_index::indexed_by< // and will be sorted by
+ // IPv6 address that are unique. That particular key is a member
+ // of the Lease6 structure, is of type IOAddress and can be accessed
+ // by doing &Lease6::addr_
+ boost::multi_index::ordered_unique<
+ boost::multi_index::member<Lease, isc::asiolink::IOAddress, &Lease::addr_>
+ >
+ >
+ > Lease4Storage; // Let the whole contraption be called Lease6Storage.
+
+ /// @brief stores IPv4 leases
+ Lease4Storage storage4_;
+
+ /// @brief stores IPv6 leases
Lease6Storage storage6_;
};
@@ -238,4 +255,3 @@ protected:
}; // end of isc namespace
#endif // MEMFILE_LEASE_MGR
-
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc
index ad6e66c..292df61 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.cc
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc
@@ -16,14 +16,16 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
#include <dhcpsrv/mysql_lease_mgr.h>
#include <boost/static_assert.hpp>
#include <mysql/mysqld_error.h>
-#include <algorithm>
#include <iostream>
#include <iomanip>
+#include <sstream>
#include <string>
#include <time.h>
@@ -193,6 +195,8 @@ TaggedStatement tagged_statements[] = {
}; // Anonymous namespace
+
+
namespace isc {
namespace dhcp {
@@ -448,9 +452,10 @@ public:
time_t cltt = 0;
MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt);
+ // note that T1 and T2 are not stored
return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_,
client_id_buffer_, client_id_length_,
- valid_lifetime_, cltt, subnet_id_)));
+ valid_lifetime_, 0, 0, cltt, subnet_id_)));
}
/// @brief Return columns in error
@@ -1112,6 +1117,9 @@ MySqlLeaseMgr::addLeaseCommon(StatementIndex stindex,
bool
MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ 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 +1129,9 @@ MySqlLeaseMgr::addLease(const Lease4Ptr& lease) {
bool
MySqlLeaseMgr::addLease(const Lease6Ptr& lease) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ 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 +1268,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_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR4).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1276,6 +1290,9 @@ MySqlLeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_HWADDR).arg(hwaddr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1285,8 +1302,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
// a "char*". (We could avoid the "const_cast" by copying the data to a
// local variable, but as the data is only being read, this introduces
// an unnecessary copy).
- unsigned long hwaddr_length = hwaddr.size();
- uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = reinterpret_cast<char*>(data);
@@ -1303,6 +1320,10 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_HWADDR)
+ .arg(subnet_id).arg(hwaddr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1312,8 +1333,8 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
// a "char*". (We could avoid the "const_cast" by copying the data to a
// local variable, but as the data is only being read, this introduces
// an unnecessary copy).
- unsigned long hwaddr_length = hwaddr.size();
- uint8_t* data = const_cast<uint8_t*>(&hwaddr[0]);
+ unsigned long hwaddr_length = hwaddr.hwaddr_.size();
+ uint8_t* data = const_cast<uint8_t*>(&hwaddr.hwaddr_[0]);
inbind[0].buffer_type = MYSQL_TYPE_BLOB;
inbind[0].buffer = reinterpret_cast<char*>(data);
@@ -1334,6 +1355,9 @@ MySqlLeaseMgr::getLease4(const HWAddr& hwaddr, SubnetID subnet_id) const {
Lease4Collection
MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_CLIENTID).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1355,6 +1379,10 @@ MySqlLeaseMgr::getLease4(const ClientId& clientid) const {
Lease4Ptr
MySqlLeaseMgr::getLease4(const ClientId& clientid, SubnetID subnet_id) const {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_SUBID_CLIENTID)
+ .arg(subnet_id).arg(clientid.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
memset(inbind, 0, sizeof(inbind));
@@ -1380,6 +1408,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_DETAIL,
+ DHCPSRV_MYSQL_GET_ADDR6).arg(addr.toText());
+
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
memset(inbind, 0, sizeof(inbind));
@@ -1403,6 +1434,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_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_DUID).arg(iaid).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[2];
@@ -1444,6 +1477,9 @@ 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_DETAIL,
+ DHCPSRV_MYSQL_GET_IAID_SUBID_DUID)
+ .arg(iaid).arg(subnet_id).arg(duid.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[3];
@@ -1511,6 +1547,9 @@ void
MySqlLeaseMgr::updateLease4(const Lease4Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE4;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR4).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange4_->createBindForSend(lease);
@@ -1533,6 +1572,9 @@ void
MySqlLeaseMgr::updateLease6(const Lease6Ptr& lease) {
const StatementIndex stindex = UPDATE_LEASE6;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_UPDATE_ADDR6).arg(lease->addr_.toText());
+
// Create the MYSQL_BIND array for the data being updated
std::vector<MYSQL_BIND> bind = exchange6_->createBindForSend(lease);
@@ -1579,6 +1621,8 @@ MySqlLeaseMgr::deleteLeaseCommon(StatementIndex stindex, MYSQL_BIND* bind) {
bool
MySqlLeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_DELETE_ADDR).arg(addr.toText());
// Set up the WHERE clause value
MYSQL_BIND inbind[1];
@@ -1632,6 +1676,9 @@ std::pair<uint32_t, uint32_t>
MySqlLeaseMgr::getVersion() const {
const StatementIndex stindex = GET_VERSION;
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL,
+ DHCPSRV_MYSQL_GET_VERSION);
+
uint32_t major; // Major version number
uint32_t minor; // Minor version number
@@ -1678,6 +1725,7 @@ MySqlLeaseMgr::getVersion() const {
void
MySqlLeaseMgr::commit() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_COMMIT);
if (mysql_commit(mysql_) != 0) {
isc_throw(DbOperationError, "commit failed: " << mysql_error(mysql_));
}
@@ -1686,6 +1734,7 @@ MySqlLeaseMgr::commit() {
void
MySqlLeaseMgr::rollback() {
+ LOG_DEBUG(dhcpsrv_logger, DHCPSRV_DBG_TRACE_DETAIL, DHCPSRV_MYSQL_ROLLBACK);
if (mysql_rollback(mysql_) != 0) {
isc_throw(DbOperationError, "rollback failed: " << mysql_error(mysql_));
}
diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.h b/src/lib/dhcpsrv/mysql_lease_mgr.h
index 68b0e1c..62f4435 100644
--- a/src/lib/dhcpsrv/mysql_lease_mgr.h
+++ b/src/lib/dhcpsrv/mysql_lease_mgr.h
@@ -15,6 +15,7 @@
#ifndef MYSQL_LEASE_MGR_H
#define MYSQL_LEASE_MGR_H
+#include <dhcp/hwaddr.h>
#include <dhcpsrv/lease_mgr.h>
#include <boost/scoped_ptr.hpp>
@@ -131,7 +132,7 @@ public:
/// programming error.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- virtual Lease4Collection getLease4(const HWAddr& hwaddr) const;
+ virtual Lease4Collection getLease4(const isc::dhcp::HWAddr& hwaddr) const;
/// @brief Returns existing IPv4 leases for specified hardware address
/// and a subnet
@@ -149,7 +150,7 @@ public:
/// programming error.
/// @throw isc::dhcp::DbOperationError An operation on the open database has
/// failed.
- virtual Lease4Ptr getLease4(const HWAddr& hwaddr,
+ virtual Lease4Ptr getLease4(const isc::dhcp::HWAddr& hwaddr,
SubnetID subnet_id) const;
/// @brief Returns existing IPv4 lease for specified client-id
diff --git a/src/lib/dhcpsrv/option_space.cc b/src/lib/dhcpsrv/option_space.cc
new file mode 100644
index 0000000..0e802a7
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.cc
@@ -0,0 +1,71 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dhcpsrv/option_space.h>
+#include <boost/algorithm/string/classification.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+
+namespace isc {
+namespace dhcp {
+
+OptionSpace::OptionSpace(const std::string& name, const bool vendor_space)
+ : name_(name), vendor_space_(vendor_space) {
+ // Check that provided option space name is valid.
+ if (!validateName(name_)) {
+ isc_throw(InvalidOptionSpace, "Invalid option space name "
+ << name_);
+ }
+}
+
+bool
+OptionSpace::validateName(const std::string& name) {
+
+ using namespace boost::algorithm;
+
+ // Allowed characters are: lower or upper case letters, digits,
+ // underscores and hyphens. Empty option spaces are not allowed.
+ if (all(name, boost::is_from_range('a', 'z') ||
+ boost::is_from_range('A', 'Z') ||
+ boost::is_digit() ||
+ boost::is_any_of(std::string("-_"))) &&
+ !name.empty() &&
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ !all(find_head(name, 1), boost::is_any_of(std::string("-_"))) &&
+ !all(find_tail(name, 1), boost::is_any_of(std::string("-_")))) {
+ return (true);
+
+ }
+ return (false);
+}
+
+OptionSpace6::OptionSpace6(const std::string& name)
+ : OptionSpace(name),
+ enterprise_number_(0) {
+}
+
+OptionSpace6::OptionSpace6(const std::string& name,
+ const uint32_t enterprise_number)
+ : OptionSpace(name, true),
+ enterprise_number_(enterprise_number) {
+}
+
+void
+OptionSpace6::setVendorSpace(const uint32_t enterprise_number) {
+ enterprise_number_ = enterprise_number;
+ OptionSpace::setVendorSpace();
+}
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
diff --git a/src/lib/dhcpsrv/option_space.h b/src/lib/dhcpsrv/option_space.h
new file mode 100644
index 0000000..9eebd76
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space.h
@@ -0,0 +1,189 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_H
+#define OPTION_SPACE_H
+
+#include <exceptions/exceptions.h>
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Exception to be thrown when invalid option space
+/// is specified.
+class InvalidOptionSpace : public Exception {
+public:
+ InvalidOptionSpace(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) { };
+};
+
+/// OptionSpace forward declaration.
+class OptionSpace;
+/// A pointer to OptionSpace object.
+typedef boost::shared_ptr<OptionSpace> OptionSpacePtr;
+/// A collection of option spaces.
+typedef std::map<std::string, OptionSpacePtr> OptionSpaceCollection;
+
+/// @brief DHCP option space.
+///
+/// This class represents single option space. The option spaces are used
+/// to group DHCP options having unique option codes. The special type
+/// of the option space is so called "vendor specific option space".
+/// It groups sub-options being sent within Vendor Encapsulated Options.
+/// For DHCPv4 it is the option with code 43. The option spaces are
+/// assigned to option instances represented by isc::dhcp::Option and
+/// other classes derived from it. Each particular option may belong to
+/// multiple option spaces.
+/// This class may be used to represent any DHCPv4 option space. If the
+/// option space is to group DHCPv4 Vendor Encapsulated Options then
+/// "vendor space" flag must be set using \ref OptionSpace::setVendorSpace
+/// or the argument passed to the constructor. In theory, this class can
+/// be also used to represent non-vendor specific DHCPv6 option space
+/// but this is discouraged. For DHCPv6 option spaces the OptionSpace6
+/// class should be used instead.
+///
+/// @note this class is intended to be used to represent DHCPv4 option
+/// spaces only. However, it hasn't been called OptionSpace4 (that would
+/// suggest that it is specific to DHCPv4) because it can be also
+/// used to represent some DHCPv6 option spaces and is a base class
+/// for \ref OptionSpace6. Thus, if one declared the container as follows:
+/// @code
+/// std::vector<OptionSpace4> container;
+/// @endcode
+/// it would suggest that the container holds DHCPv4 option spaces while
+/// it could hold both DHCPv4 and DHCPv6 option spaces as the OptionSpace6
+/// object could be upcast to OptionSpace4. This confusion does not appear
+/// when OptionSpace is used as a name for the base class.
+class OptionSpace {
+public:
+
+ /// @brief Constructor.
+ ///
+ /// @param name option space name.
+ /// @param vendor_space boolean value that indicates that the object
+ /// describes the vendor specific option space.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref validateName function to check that the specified name is
+ /// correct.
+ OptionSpace(const std::string& name, const bool vendor_space = false);
+
+ /// @brief Return option space name.
+ ///
+ /// @return option space name.
+ const std::string& getName() const { return (name_); }
+
+ /// @brief Mark option space as non-vendor space.
+ void clearVendorSpace() {
+ vendor_space_ = false;
+ }
+
+ /// @brief Check if option space is vendor specific.
+ ///
+ /// @return boolean value that indicates if the object describes
+ /// the vendor specific option space.
+ bool isVendorSpace() const { return (vendor_space_); }
+
+ /// @brief Mark option space as vendor specific.
+ void setVendorSpace() {
+ vendor_space_ = true;
+ }
+
+ /// @brief Checks that the provided option space name is valid.
+ ///
+ /// It is expected that option space name consists of upper or
+ /// lower case letters or digits. Also, it may contain underscores
+ /// or dashes. Other characters are prohibited. The empty option
+ /// space names are invalid.
+ ///
+ /// @param name option space name to be validated.
+ ///
+ /// @return true if the option space is valid, else it returns false.
+ static bool validateName(const std::string& name);
+
+private:
+ std::string name_; ///< Holds option space name.
+
+ bool vendor_space_; ///< Is this the vendor space?
+
+};
+
+/// @brief DHCPv6 option space with enterprise number assigned.
+///
+/// This class extends the base class with the support for enterprise numbers.
+/// The enterprise numbers are assigned by IANA to various organizations
+/// and they are carried as uint32_t integers in DHCPv6 Vendor Specific
+/// Information Options (VSIO). For more information refer to RFC3315.
+/// All option spaces that group VSIO options must have enterprise number
+/// set. It can be set using a constructor or \ref setVendorSpace function.
+/// The extra functionality of this class (enterprise numbers) allows to
+/// represent DHCPv6 vendor-specific option spaces but this class is also
+/// intended to be used for all other DHCPv6 option spaces. That way all
+/// DHCPv6 option spaces can be stored in the container holding OptionSpace6
+/// objects. Also, it is easy to mark vendor-specific option space as non-vendor
+/// specific option space (and the other way around) without a need to cast
+/// between OptionSpace and OptionSpace6 types.
+class OptionSpace6 : public OptionSpace {
+public:
+
+ /// @brief Constructor for non-vendor-specific options.
+ ///
+ /// This constructor marks option space as non-vendor specific.
+ ///
+ /// @param name option space name.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name);
+
+ /// @brief Constructor for vendor-specific options.
+ ///
+ /// This constructor marks option space as vendor specific and sets
+ /// enterprise number to a given value.
+ ///
+ /// @param name option space name.
+ /// @param enterprise_number enterprise number.
+ ///
+ /// @throw isc::dhcp::InvalidOptionSpace if given option space name
+ /// contains invalid characters or is empty. This constructor uses
+ /// \ref OptionSpace::validateName function to check that the specified
+ /// name is correct.
+ OptionSpace6(const std::string& name, const uint32_t enterprise_number);
+
+ /// @brief Return enterprise number for the option space.
+ ///
+ /// @return enterprise number.
+ uint32_t getEnterpriseNumber() const { return (enterprise_number_); }
+
+ /// @brief Mark option space as vendor specific.
+ ///
+ /// @param enterprise_number enterprise number.
+ void setVendorSpace(const uint32_t enterprise_number);
+
+private:
+
+ uint32_t enterprise_number_; ///< IANA assigned enterprise number.
+};
+
+} // namespace isc::dhcp
+} // namespace isc
+
+#endif // OPTION_SPACE_H
diff --git a/src/lib/dhcpsrv/option_space_container.h b/src/lib/dhcpsrv/option_space_container.h
new file mode 100644
index 0000000..f90bedd
--- /dev/null
+++ b/src/lib/dhcpsrv/option_space_container.h
@@ -0,0 +1,102 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef OPTION_SPACE_CONTAINER_H
+#define OPTION_SPACE_CONTAINER_H
+
+#include <list>
+#include <string>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Simple container for option spaces holding various items.
+///
+/// This helper class is used to store items of various types in
+/// that are grouped by option space names. Each option space is
+/// mapped to a container that holds items which specifically can
+/// be OptionDefinition objects or Subnet::OptionDescriptor structures.
+///
+/// @tparam ContainerType of the container holding items within
+/// option space.
+/// @tparam ItemType type of the item being held by the container.
+template<typename ContainerType, typename ItemType>
+class OptionSpaceContainer {
+public:
+
+ /// Pointer to the container.
+ typedef boost::shared_ptr<ContainerType> ItemsContainerPtr;
+
+ /// @brief Adds a new item to the option_space.
+ ///
+ /// @param item reference to the item being added.
+ /// @param name of the option space.
+ void addItem(const ItemType& item, const std::string& option_space) {
+ ItemsContainerPtr items = getItems(option_space);
+ items->push_back(item);
+ option_space_map_[option_space] = items;
+ }
+
+ /// @brief Get all items for the particular option space.
+ ///
+ /// @warning when there are no items for the specified option
+ /// space an empty container is created and returned. However
+ /// this container is not added to the list of option spaces.
+ ///
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to the container holding items.
+ ItemsContainerPtr getItems(const std::string& option_space) const {
+ const typename OptionSpaceMap::const_iterator& items =
+ option_space_map_.find(option_space);
+ if (items == option_space_map_.end()) {
+ return (ItemsContainerPtr(new ContainerType()));
+ }
+ return (items->second);
+ }
+
+ /// @brief Get a list of existing option spaces.
+ ///
+ /// @return a list of option spaces.
+ ///
+ /// @todo This function is likely to be removed once
+ /// we create a structore of OptionSpaces defined
+ /// through the configuration manager.
+ std::list<std::string> getOptionSpaceNames() {
+ std::list<std::string> names;
+ for (typename OptionSpaceMap::const_iterator space =
+ option_space_map_.begin();
+ space != option_space_map_.end(); ++space) {
+ names.push_back(space->first);
+ }
+ return (names);
+ }
+
+ /// @brief Remove all items from the container.
+ void clearItems() {
+ option_space_map_.clear();
+ }
+
+private:
+
+ /// A map holding container (option space name is the key).
+ typedef std::map<std::string, ItemsContainerPtr> OptionSpaceMap;
+ OptionSpaceMap option_space_map_;
+};
+
+
+} // end of isc::dhcp namespace
+} // end of isc namespace
+
+#endif // OPTION_SPACE_CONTAINER_H
diff --git a/src/lib/dhcpsrv/pool.h b/src/lib/dhcpsrv/pool.h
index eb5e5e2..e8dc3e3 100644
--- a/src/lib/dhcpsrv/pool.h
+++ b/src/lib/dhcpsrv/pool.h
@@ -179,6 +179,13 @@ typedef boost::shared_ptr<Pool6> Pool6Ptr;
/// @brief a container for IPv6 Pools
typedef std::vector<Pool6Ptr> Pool6Collection;
+/// @brief a pointer to either IPv4 or IPv6 Pool
+typedef boost::shared_ptr<Pool> PoolPtr;
+
+/// @brief a container for either IPv4 or IPv6 Pools
+typedef std::vector<PoolPtr> PoolCollection;
+
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc
index 3f8733f..0443a33 100644
--- a/src/lib/dhcpsrv/subnet.cc
+++ b/src/lib/dhcpsrv/subnet.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -44,14 +44,45 @@ bool Subnet::inRange(const isc::asiolink::IOAddress& addr) const {
}
void
-Subnet::addOption(OptionPtr& option, bool persistent /* = false */) {
+Subnet::addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space) {
+ // @todo Once the #2313 is merged we need to use the OptionSpace object to
+ // validate the option space name here. For now, let's check that the name
+ // is not empty as the empty namespace has a special meaning here - it is
+ // returned when desired namespace is not found when getOptions is called.
+ if (option_space.empty()) {
+ isc_throw(isc::BadValue, "option space name must not be empty");
+ }
validateOption(option);
- options_.push_back(OptionDescriptor(option, persistent));
+
+ // Actually add new option descriptor.
+ option_spaces_.addItem(OptionDescriptor(option, persistent), option_space);
}
void
Subnet::delOptions() {
- options_.clear();
+ option_spaces_.clearItems();
+}
+
+Subnet::OptionContainerPtr
+Subnet::getOptionDescriptors(const std::string& option_space) const {
+ return (option_spaces_.getItems(option_space));
+}
+
+Subnet::OptionDescriptor
+Subnet::getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code) {
+ OptionContainerPtr options = getOptionDescriptors(option_space);
+ if (!options || options->empty()) {
+ return (OptionDescriptor(false));
+ }
+ const OptionContainerTypeIndex& idx = options->get<1>();
+ const OptionContainerTypeRange& range = idx.equal_range(option_code);
+ if (std::distance(range.first, range.second) == 0) {
+ return (OptionDescriptor(false));
+ }
+
+ return (*range.first);
}
std::string Subnet::toText() const {
@@ -71,14 +102,14 @@ Subnet4::Subnet4(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet4::addPool4(const Pool4Ptr& pool) {
+void Subnet::addPool(const PoolPtr& pool) {
IOAddress first_addr = pool->getFirstAddress();
IOAddress last_addr = pool->getLastAddress();
if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool4 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet4");
+ isc_throw(BadValue, "Pool (" << first_addr.toText() << "-" << last_addr.toText()
+ << " does not belong in this (" << prefix_.toText() << "/"
+ << static_cast<int>(prefix_len_) << ") subnet4");
}
/// @todo: Check that pools do not overlap
@@ -86,9 +117,10 @@ void Subnet4::addPool4(const Pool4Ptr& pool) {
pools_.push_back(pool);
}
-Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool4Ptr candidate;
- for (Pool4Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+PoolPtr Subnet::getPool(isc::asiolink::IOAddress hint) {
+
+ PoolPtr candidate;
+ for (PoolCollection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
// if we won't find anything better, then let's just use the first pool
if (!candidate) {
@@ -104,6 +136,7 @@ Pool4Ptr Subnet4::getPool4(const isc::asiolink::IOAddress& hint /* = IOAddress("
return (candidate);
}
+
void
Subnet4::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -113,14 +146,14 @@ Subnet4::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet4::inPool(const isc::asiolink::IOAddress& addr) const {
+bool Subnet::inPool(const isc::asiolink::IOAddress& addr) const {
// Let's start with checking if it even belongs to that subnet.
if (!inRange(addr)) {
return (false);
}
- for (Pool4Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
+ for (PoolCollection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
if ((*pool)->inRange(addr)) {
return (true);
}
@@ -142,39 +175,6 @@ Subnet6::Subnet6(const isc::asiolink::IOAddress& prefix, uint8_t length,
}
}
-void Subnet6::addPool6(const Pool6Ptr& pool) {
- IOAddress first_addr = pool->getFirstAddress();
- IOAddress last_addr = pool->getLastAddress();
-
- if (!inRange(first_addr) || !inRange(last_addr)) {
- isc_throw(BadValue, "Pool6 (" << first_addr.toText() << "-" << last_addr.toText()
- << " does not belong in this (" << prefix_ << "/" << prefix_len_
- << ") subnet6");
- }
-
- /// @todo: Check that pools do not overlap
-
- pools_.push_back(pool);
-}
-
-Pool6Ptr Subnet6::getPool6(const isc::asiolink::IOAddress& hint /* = IOAddress("::")*/ ) {
- Pool6Ptr candidate;
- for (Pool6Collection::iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
-
- // if we won't find anything better, then let's just use the first pool
- if (!candidate) {
- candidate = *pool;
- }
-
- // if the client provided a pool and there's a pool that hint is valid in,
- // then let's use that pool
- if ((*pool)->inRange(hint)) {
- return (*pool);
- }
- }
- return (candidate);
-}
-
void
Subnet6::validateOption(const OptionPtr& option) const {
if (!option) {
@@ -184,21 +184,15 @@ Subnet6::validateOption(const OptionPtr& option) const {
}
}
-bool Subnet6::inPool(const isc::asiolink::IOAddress& addr) const {
- // Let's start with checking if it even belongs to that subnet.
- if (!inRange(addr)) {
- return (false);
- }
+void Subnet6::setIface(const std::string& iface_name) {
+ iface_ = iface_name;
+}
- for (Pool6Collection::const_iterator pool = pools_.begin(); pool != pools_.end(); ++pool) {
- if ((*pool)->inRange(addr)) {
- return (true);
- }
- }
- // there's no pool that address belongs to
- return (false);
+std::string Subnet6::getIface() const {
+ return (iface_);
}
+
} // end of isc::dhcp namespace
} // end of isc namespace
diff --git a/src/lib/dhcpsrv/subnet.h b/src/lib/dhcpsrv/subnet.h
index c7d7ac7..471fb03 100644
--- a/src/lib/dhcpsrv/subnet.h
+++ b/src/lib/dhcpsrv/subnet.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -24,6 +24,7 @@
#include <asiolink/io_address.h>
#include <dhcp/option.h>
+#include <dhcpsrv/option_space_container.h>
#include <dhcpsrv/pool.h>
#include <dhcpsrv/triplet.h>
@@ -78,6 +79,9 @@ public:
: option(OptionPtr()), persistent(persist) {};
};
+ /// A pointer to option descriptor.
+ typedef boost::shared_ptr<OptionDescriptor> OptionDescriptorPtr;
+
/// @brief Extractor class to extract key with another key.
///
/// This class solves the problem of accessing index key values
@@ -198,6 +202,8 @@ public:
>
> OptionContainer;
+ // Pointer to the OptionContainer object.
+ typedef boost::shared_ptr<OptionContainer> OptionContainerPtr;
/// Type of the index #1 - option type.
typedef OptionContainer::nth_index<1>::type OptionContainerTypeIndex;
/// Pair of iterators to represent the range of options having the
@@ -216,9 +222,11 @@ public:
/// @param option option instance.
/// @param persistent if true, send an option regardless if client
/// requested it or not.
+ /// @param option_space name of the option space to add an option to.
///
/// @throw isc::BadValue if invalid option provided.
- void addOption(OptionPtr& option, bool persistent = false);
+ void addOption(OptionPtr& option, bool persistent,
+ const std::string& option_space);
/// @brief Delete all options configured for the subnet.
void delOptions();
@@ -235,7 +243,7 @@ public:
/// @param addr this address will be checked if it belongs to any pools in
/// that subnet
/// @return true if the address is in any of the pools
- virtual bool inPool(const isc::asiolink::IOAddress& addr) const = 0;
+ bool inPool(const isc::asiolink::IOAddress& addr) const;
/// @brief return valid-lifetime for addresses in that prefix
Triplet<uint32_t> getValid() const {
@@ -252,14 +260,24 @@ public:
return (t2_);
}
- /// @brief Return a collection of options.
+ /// @brief Return a collection of option descriptors.
///
- /// @return reference to collection of options configured for a subnet.
- /// The returned reference is valid as long as the Subnet object which
- /// returned it still exists.
- const OptionContainer& getOptions() const {
- return (options_);
- }
+ /// @param option_space name of the option space.
+ ///
+ /// @return pointer to collection of options configured for a subnet.
+ OptionContainerPtr
+ getOptionDescriptors(const std::string& option_space) const;
+
+ /// @brief Return single option descriptor.
+ ///
+ /// @param option_space name of the option space.
+ /// @param option_code code of the option to be returned.
+ ///
+ /// @return option descriptor found for the specified option space
+ /// and option code.
+ OptionDescriptor
+ getOptionDescriptor(const std::string& option_space,
+ const uint16_t option_code);
/// @brief returns the last address that was tried from this pool
///
@@ -298,6 +316,37 @@ public:
return (std::make_pair(prefix_, prefix_len_));
}
+ /// @brief Adds a new pool.
+ /// @param pool pool to be added
+ void addPool(const PoolPtr& pool);
+
+ /// @brief Returns a pool that specified address belongs to
+ ///
+ /// @param addr address that the returned pool should cover (optional)
+ /// @return Pointer to found Pool4 or Pool6 (or NULL)
+ PoolPtr getPool(isc::asiolink::IOAddress addr);
+
+ /// @brief Returns a pool without any address specified
+ /// @return returns one of the pools defined
+ PoolPtr getPool() {
+ return (getPool(default_pool()));
+ }
+
+ /// @brief Returns the default address that will be used for pool selection
+ ///
+ /// It must be implemented in derived classes (should return :: for Subnet6
+ /// and 0.0.0.0 for Subnet4)
+ virtual isc::asiolink::IOAddress default_pool() const = 0;
+
+ /// @brief returns all pools
+ ///
+ /// The reference is only valid as long as the object that returned it.
+ ///
+ /// @return a collection of all pools
+ const PoolCollection& getPools() const {
+ return pools_;
+ }
+
/// @brief returns textual representation of the subnet (e.g. "2001:db8::/64")
///
/// @return textual representation
@@ -338,6 +387,9 @@ protected:
/// a Subnet4 or Subnet6.
SubnetID id_;
+ /// @brief collection of pools in that list
+ PoolCollection pools_;
+
/// @brief a prefix of the subnet
isc::asiolink::IOAddress prefix_;
@@ -353,9 +405,6 @@ protected:
/// @brief a tripet (min/default/max) holding allowed valid lifetime values
Triplet<uint32_t> valid_;
- /// @brief a collection of DHCP options configured for a subnet.
- OptionContainer options_;
-
/// @brief last allocated address
///
/// This is the last allocated address that was previously allocated from
@@ -366,8 +415,22 @@ protected:
/// that purpose it should be only considered a help that should not be
/// fully trusted.
isc::asiolink::IOAddress last_allocated_;
+
+ /// @brief Name of the network interface (if connected directly)
+ std::string iface_;
+
+private:
+
+ /// A collection of option spaces grouping option descriptors.
+ typedef OptionSpaceContainer<OptionContainer,
+ OptionDescriptor> OptionSpaceCollection;
+ OptionSpaceCollection option_spaces_;
+
};
+/// @brief A generic pointer to either Subnet4 or Subnet6 object
+typedef boost::shared_ptr<Subnet> SubnetPtr;
+
/// @brief A configuration holder for IPv4 subnet.
///
/// This class represents an IPv4 subnet.
@@ -386,34 +449,6 @@ public:
const Triplet<uint32_t>& t2,
const Triplet<uint32_t>& valid_lifetime);
- /// @brief Returns a pool that specified address belongs to
- ///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool4 (or NULL)
- Pool4Ptr getPool4(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("0.0.0.0"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool4(const Pool4Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that returned it.
- ///
- /// @return a collection of all pools
- const Pool4Collection& getPools() const {
- return pools_;
- }
-
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
-
protected:
/// @brief Check if option is valid and can be added to a subnet.
@@ -423,8 +458,11 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
- /// @brief collection of pools in that list
- Pool4Collection pools_;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv4 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("0.0.0.0"));
+ }
};
/// @brief A pointer to a Subnet4 object
@@ -461,34 +499,17 @@ public:
return (preferred_);
}
- /// @brief Returns a pool that specified address belongs to
+ /// @brief sets name of the network interface for directly attached networks
///
- /// @param hint address that the returned pool should cover (optional)
- /// @return Pointer to found pool6 (or NULL)
- Pool6Ptr getPool6(const isc::asiolink::IOAddress& hint =
- isc::asiolink::IOAddress("::"));
-
- /// @brief Adds a new pool.
- /// @param pool pool to be added
- void addPool6(const Pool6Ptr& pool);
-
- /// @brief returns all pools
- ///
- /// The reference is only valid as long as the object that
- /// returned it.
- ///
- /// @return a collection of all pools
- const Pool6Collection& getPools() const {
- return pools_;
- }
+ /// A subnet may be reachable directly (not via relays). In DHCPv6 it is not
+ /// possible to decide that based on addresses assigned to network interfaces,
+ /// as DHCPv6 operates on link-local (and site local) addresses.
+ /// @param iface_name name of the interface
+ void setIface(const std::string& iface_name);
- /// @brief checks if the specified address is in pools
- ///
- /// See the description in \ref Subnet::inPool().
- ///
- /// @param addr this address will be checked if it belongs to any pools in that subnet
- /// @return true if the address is in any of the pools
- bool inPool(const isc::asiolink::IOAddress& addr) const;
+ /// @brief network interface name used to reach subnet (or "" for remote subnets)
+ /// @return network interface name for directly attached subnets or ""
+ std::string getIface() const;
protected:
@@ -499,6 +520,12 @@ protected:
/// @throw isc::BadValue if provided option is invalid.
virtual void validateOption(const OptionPtr& option) const;
+ /// @brief Returns default address for pool selection
+ /// @return ANY IPv6 address
+ virtual isc::asiolink::IOAddress default_pool() const {
+ return (isc::asiolink::IOAddress("::"));
+ }
+
/// @brief collection of pools in that list
Pool6Collection pools_;
diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am
index 11e1d75..dfe3e78 100644
--- a/src/lib/dhcpsrv/tests/Makefile.am
+++ b/src/lib/dhcpsrv/tests/Makefile.am
@@ -36,6 +36,7 @@ libdhcpsrv_unittests_SOURCES += memfile_lease_mgr_unittest.cc
if HAVE_MYSQL
libdhcpsrv_unittests_SOURCES += mysql_lease_mgr_unittest.cc
endif
+libdhcpsrv_unittests_SOURCES += option_space_unittest.cc
libdhcpsrv_unittests_SOURCES += pool_unittest.cc
libdhcpsrv_unittests_SOURCES += schema_copy.h
libdhcpsrv_unittests_SOURCES += subnet_unittest.cc
diff --git a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
index 19f08a9..cdd59df 100644
--- a/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
+++ b/src/lib/dhcpsrv/tests/addr_utilities_unittest.cc
@@ -16,6 +16,7 @@
#include <config.h>
#include <dhcpsrv/addr_utilities.h>
+#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -28,6 +29,8 @@ using namespace std;
using namespace isc::dhcp;
using namespace isc::asiolink;
+namespace {
+
// This test verifies that lastAddrInPrefix is able to handle IPv4 operations.
TEST(AddrUtilitiesTest, lastAddrInPrefix4) {
IOAddress addr1("192.0.2.1");
@@ -154,3 +157,47 @@ TEST(AddrUtilitiesTest, firstAddrInPrefix6) {
EXPECT_EQ("2001::ff80", firstAddrInPrefix(addr2, 121).toText());
EXPECT_EQ("2001::ff00", firstAddrInPrefix(addr2, 120).toText());
}
+
+// Checks if IPv4 netmask is generated properly
+TEST(AddrUtilitiesTest, getNetmask4) {
+ EXPECT_EQ("0.0.0.0", getNetmask4(0).toText());
+ EXPECT_EQ("128.0.0.0", getNetmask4(1).toText());
+ EXPECT_EQ("192.0.0.0", getNetmask4(2).toText());
+ EXPECT_EQ("224.0.0.0", getNetmask4(3).toText());
+ EXPECT_EQ("240.0.0.0", getNetmask4(4).toText());
+ EXPECT_EQ("248.0.0.0", getNetmask4(5).toText());
+ EXPECT_EQ("252.0.0.0", getNetmask4(6).toText());
+ EXPECT_EQ("254.0.0.0", getNetmask4(7).toText());
+ EXPECT_EQ("255.0.0.0", getNetmask4(8).toText());
+
+ EXPECT_EQ("255.128.0.0", getNetmask4(9).toText());
+ EXPECT_EQ("255.192.0.0", getNetmask4(10).toText());
+ EXPECT_EQ("255.224.0.0", getNetmask4(11).toText());
+ EXPECT_EQ("255.240.0.0", getNetmask4(12).toText());
+ EXPECT_EQ("255.248.0.0", getNetmask4(13).toText());
+ EXPECT_EQ("255.252.0.0", getNetmask4(14).toText());
+ EXPECT_EQ("255.254.0.0", getNetmask4(15).toText());
+ EXPECT_EQ("255.255.0.0", getNetmask4(16).toText());
+
+ EXPECT_EQ("255.255.128.0", getNetmask4(17).toText());
+ EXPECT_EQ("255.255.192.0", getNetmask4(18).toText());
+ EXPECT_EQ("255.255.224.0", getNetmask4(19).toText());
+ EXPECT_EQ("255.255.240.0", getNetmask4(20).toText());
+ EXPECT_EQ("255.255.248.0", getNetmask4(21).toText());
+ EXPECT_EQ("255.255.252.0", getNetmask4(22).toText());
+ EXPECT_EQ("255.255.254.0", getNetmask4(23).toText());
+ EXPECT_EQ("255.255.255.0", getNetmask4(24).toText());
+
+ EXPECT_EQ("255.255.255.128", getNetmask4(25).toText());
+ EXPECT_EQ("255.255.255.192", getNetmask4(26).toText());
+ EXPECT_EQ("255.255.255.224", getNetmask4(27).toText());
+ EXPECT_EQ("255.255.255.240", getNetmask4(28).toText());
+ EXPECT_EQ("255.255.255.248", getNetmask4(29).toText());
+ EXPECT_EQ("255.255.255.252", getNetmask4(30).toText());
+ EXPECT_EQ("255.255.255.254", getNetmask4(31).toText());
+ EXPECT_EQ("255.255.255.255", getNetmask4(32).toText());
+
+ EXPECT_THROW(getNetmask4(33), isc::BadValue);
+}
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
index 95eabb2..078998d 100644
--- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
+++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc
@@ -16,6 +16,7 @@
#include <asiolink/io_address.h>
#include <dhcp/duid.h>
+#include <dhcp/dhcp4.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
@@ -30,7 +31,7 @@
#include <iostream>
#include <sstream>
-#include <map>
+#include <set>
#include <time.h>
using namespace std;
@@ -41,20 +42,33 @@ using namespace isc::dhcp::test;
namespace {
+/// @brief Allocation engine with some internal methods exposed
class NakedAllocEngine : public AllocEngine {
public:
+
+ /// @brief the sole constructor
+ /// @param engine_type specifies engine type (e.g. iterative)
+ /// @param attempts number of lease selection attempts before giving up
NakedAllocEngine(AllocEngine::AllocType engine_type, unsigned int attempts)
:AllocEngine(engine_type, attempts) {
}
+
+ // Expose internal classes for testing purposes
using AllocEngine::Allocator;
using AllocEngine::IterativeAllocator;
};
-// empty class for now, but may be extended once Addr6 becomes bigger
-class AllocEngineTest : public ::testing::Test {
+/// @brief Used in Allocation Engine tests for IPv6
+class AllocEngine6Test : public ::testing::Test {
public:
- AllocEngineTest() {
- duid_ = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0x42)));
+
+ /// @brief Default constructor
+ ///
+ /// Sets duid_, iaid_, subnet_, pool_ fields to example values used
+ /// in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine6Test() {
+ duid_ = DuidPtr(new DUID(vector<uint8_t>(8, 0x42)));
iaid_ = 42;
// instantiate cfg_mgr
@@ -63,12 +77,15 @@ public:
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1::10"),
IOAddress("2001:db8:1::20")));
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
factory_.create("type=memfile");
}
+ /// @brief checks if Lease6 matches expected configuration
+ ///
+ /// @param lease lease to be checked
void checkLease6(const Lease6Ptr& lease) {
// that is belongs to the right subnet
EXPECT_EQ(lease->subnet_id_, subnet_->getID());
@@ -88,20 +105,80 @@ public:
// @todo: check cltt
}
- ~AllocEngineTest() {
+ ~AllocEngine6Test() {
factory_.destroy();
}
- DuidPtr duid_;
- uint32_t iaid_;
- Subnet6Ptr subnet_;
- Pool6Ptr pool_;
- LeaseMgrFactory factory_;
+ DuidPtr duid_; ///< client-identifier (value used in tests)
+ uint32_t iaid_; ///< IA identifier (value used in tests)
+ Subnet6Ptr subnet_; ///< subnet6 (used in tests)
+ Pool6Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
+};
+
+/// @brief Used in Allocation Engine tests for IPv4
+class AllocEngine4Test : public ::testing::Test {
+public:
+
+ /// @brief Default constructor
+ ///
+ /// Sets clientid_, hwaddr_, subnet_, pool_ fields to example values
+ /// used in many tests, initializes cfg_mgr configuration and creates
+ /// lease database.
+ AllocEngine4Test() {
+ clientid_ = ClientIdPtr(new ClientId(vector<uint8_t>(8, 0x44)));
+ static uint8_t mac[] = { 0, 1, 22, 33, 44, 55};
+
+ // Let's use odd hardware type to check if there is no Ethernet
+ // hardcoded anywhere.
+ hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI));
+
+ // instantiate cfg_mgr
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"),
+ IOAddress("192.0.2.109")));
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ factory_.create("type=memfile");
+ }
+
+ /// @brief checks if Lease4 matches expected configuration
+ ///
+ /// @param lease lease to be checked
+ void checkLease4(const Lease4Ptr& lease) {
+ // that is belongs to the right subnet
+ EXPECT_EQ(lease->subnet_id_, subnet_->getID());
+ EXPECT_TRUE(subnet_->inRange(lease->addr_));
+ EXPECT_TRUE(subnet_->inPool(lease->addr_));
+
+ // that it have proper parameters
+ EXPECT_EQ(subnet_->getValid(), lease->valid_lft_);
+ EXPECT_EQ(subnet_->getT1(), lease->t1_);
+ EXPECT_EQ(subnet_->getT2(), lease->t2_);
+ EXPECT_TRUE(false == lease->fqdn_fwd_);
+ EXPECT_TRUE(false == lease->fqdn_rev_);
+ EXPECT_TRUE(*lease->client_id_ == *clientid_);
+ EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_);
+ // @todo: check cltt
+ }
+
+ ~AllocEngine4Test() {
+ factory_.destroy();
+ }
+
+ ClientIdPtr clientid_; ///< client-identifier (value used in tests)
+ HWAddrPtr hwaddr_; ///< hardware address (value used in tests)
+ Subnet4Ptr subnet_; ///< subnet4 (used in tests)
+ Pool4Ptr pool_; ///< pool belonging to subnet_
+ LeaseMgrFactory factory_; ///< pointer to LeaseMgr factory
};
// This test checks if the Allocation Engine can be instantiated and that it
// parses parameters string properly.
-TEST_F(AllocEngineTest, constructor) {
+TEST_F(AllocEngine6Test, constructor) {
boost::scoped_ptr<AllocEngine> x;
// Hashed and random allocators are not supported yet
@@ -112,7 +189,7 @@ TEST_F(AllocEngineTest, constructor) {
}
// This test checks if the simple allocation can succeed
-TEST_F(AllocEngineTest, simpleAlloc) {
+TEST_F(AllocEngine6Test, simpleAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -135,7 +212,7 @@ TEST_F(AllocEngineTest, simpleAlloc) {
}
// This test checks if the fake allocation (for SOLICIT) can succeed
-TEST_F(AllocEngineTest, fakeAlloc) {
+TEST_F(AllocEngine6Test, fakeAlloc6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -156,7 +233,7 @@ TEST_F(AllocEngineTest, fakeAlloc) {
// This test checks if the allocation with a hint that is valid (in range,
// in pool and free) can succeed
-TEST_F(AllocEngineTest, allocWithValidHint) {
+TEST_F(AllocEngine6Test, allocWithValidHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -184,15 +261,16 @@ TEST_F(AllocEngineTest, allocWithValidHint) {
// This test checks if the allocation with a hint that is in range,
// in pool, but is currently used) can succeed
-TEST_F(AllocEngineTest, allocWithUsedHint) {
+TEST_F(AllocEngine6Test, allocWithUsedHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
// let's create a lease and put it in the LeaseMgr
DuidPtr duid2 = boost::shared_ptr<DUID>(new DUID(vector<uint8_t>(8, 0xff)));
+ time_t now = time(NULL);
Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"),
- duid2, 1, 2, 3, 4, 5, subnet_->getID()));
+ duid2, 1, 2, 3, 4, now, subnet_->getID()));
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
// another client comes in and request an address that is in pool, but
@@ -223,7 +301,7 @@ TEST_F(AllocEngineTest, allocWithUsedHint) {
// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
-TEST_F(AllocEngineTest, allocBogusHint) {
+TEST_F(AllocEngine6Test, allocBogusHint6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -253,7 +331,7 @@ TEST_F(AllocEngineTest, allocBogusHint) {
// This test verifies that the allocator picks addresses that belong to the
// pool
-TEST_F(AllocEngineTest, IterativeAllocator) {
+TEST_F(AllocEngine6Test, IterativeAllocator) {
boost::scoped_ptr<NakedAllocEngine::Allocator>
alloc(new NakedAllocEngine::IterativeAllocator());
@@ -267,7 +345,7 @@ TEST_F(AllocEngineTest, IterativeAllocator) {
// This test verifies that the iterative allocator really walks over all addresses
// in all pools in specified subnet. It also must not pick the same address twice
// unless it runs out of pool space and must start over.
-TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
+TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) {
NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
// let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
@@ -280,14 +358,14 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
Pool6Ptr pool(new Pool6(Pool6::TYPE_IA, IOAddress(min.str()),
IOAddress(max.str())));
// cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
- subnet_->addPool6(pool);
+ subnet_->addPool(pool);
}
int total = 17 + 8*9; // first pool (::10 - ::20) has 17 addresses in it,
// there are 8 extra pools with 9 addresses in each.
// Let's keep picked addresses here and check their uniqueness.
- std::map<IOAddress, int> generated_addrs;
+ std::set<IOAddress> generated_addrs;
int cnt = 0;
while (++cnt) {
IOAddress candidate = alloc->pickAddress(subnet_, duid_, IOAddress("::"));
@@ -300,7 +378,7 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
if (generated_addrs.find(candidate) == generated_addrs.end()) {
// we haven't had this
- generated_addrs[candidate] = 0;
+ generated_addrs.insert(candidate);
} else {
// we have seen this address before. That should mean that we
// iterated over all addresses.
@@ -322,7 +400,7 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) {
}
// This test checks if really small pools are working
-TEST_F(AllocEngineTest, smallPool) {
+TEST_F(AllocEngine6Test, smallPool6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -334,7 +412,7 @@ TEST_F(AllocEngineTest, smallPool) {
// Create configuration similar to other tests, but with a single address pool
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
Lease6Ptr lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
@@ -358,7 +436,7 @@ TEST_F(AllocEngineTest, smallPool) {
// This test checks if all addresses in a pool are currently used, the attempt
// to find out a new lease fails.
-TEST_F(AllocEngineTest, outOfAddresses) {
+TEST_F(AllocEngine6Test, outOfAddresses6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -370,7 +448,7 @@ TEST_F(AllocEngineTest, outOfAddresses) {
// Create configuration similar to other tests, but with a single address pool
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
// Just a different duid
@@ -389,7 +467,7 @@ TEST_F(AllocEngineTest, outOfAddresses) {
}
// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
-TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
+TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -401,7 +479,7 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
// Create configuration similar to other tests, but with a single address pool
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
// Just a different duid
@@ -413,6 +491,9 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
lease->valid_lft_ = 495; // Lease was valid for 495 seconds
ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+ // Make sure that we really created expired lease
+ ASSERT_TRUE(lease->expired());
+
// CASE 1: Asking for any address
lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"),
true);
@@ -432,7 +513,7 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) {
}
// This test checks if an expired lease can be reused in REQUEST (actual allocation)
-TEST_F(AllocEngineTest, requestReuseExpiredLease) {
+TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
boost::scoped_ptr<AllocEngine> engine;
ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
ASSERT_TRUE(engine);
@@ -444,7 +525,7 @@ TEST_F(AllocEngineTest, requestReuseExpiredLease) {
// Create configuration similar to other tests, but with a single address pool
subnet_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
pool_ = Pool6Ptr(new Pool6(Pool6::TYPE_IA, addr, addr)); // just a single address
- subnet_->addPool6(pool_);
+ subnet_->addPool(pool_);
cfg_mgr.addSubnet6(subnet_);
// Let's create an expired lease
@@ -473,4 +554,412 @@ TEST_F(AllocEngineTest, requestReuseExpiredLease) {
detailCompareLease(lease, from_mgr);
}
+// --- IPv4 ---
+
+// This test checks if the simple IPv4 allocation can succeed
+TEST_F(AllocEngine4Test, simpleAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if the fake allocation (for DISCOVER) can succeed
+TEST_F(AllocEngine4Test, fakeAlloc4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("0.0.0.0"), true);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is NOT in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_FALSE(from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is valid (in range,
+// in pool and free) can succeed
+TEST_F(AllocEngine4Test, allocWithValidHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.105"),
+ false);
+
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should get what we asked for
+ EXPECT_EQ(lease->addr_.toText(), "192.0.2.105");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is in range,
+// in pool, but is currently used) can succeed
+TEST_F(AllocEngine4Test, allocWithUsedHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Let's create a lease and put it in the LeaseMgr
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, sizeof(hwaddr2),
+ clientid2, sizeof(clientid2), 1, 2, 3, now, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+ // Another client comes in and request an address that is in pool, but
+ // unfortunately it is used already. The same address must not be allocated
+ // twice.
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("192.0.2.106"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // Allocated address must be different
+ EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText());
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test checks if the allocation with a hint that is out the blue
+// can succeed. The invalid hint should be ignored completely.
+TEST_F(AllocEngine4Test, allocBogusHint4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ // Client would like to get a 3000::abc lease, which does not belong to any
+ // supported lease. Allocation engine should ignore it and carry on
+ // with the normal allocation
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress("10.1.1.1"),
+ false);
+ // Check that we got a lease
+ ASSERT_TRUE(lease);
+
+ // We should NOT get what we asked for, because it is used already
+ EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1");
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+
+// This test verifies that the allocator picks addresses that belong to the
+// pool
+TEST_F(AllocEngine4Test, IterativeAllocator) {
+ boost::scoped_ptr<NakedAllocEngine::Allocator>
+ alloc(new NakedAllocEngine::IterativeAllocator());
+
+ for (int i = 0; i < 1000; ++i) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_,
+ IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+ }
+}
+
+
+// This test verifies that the iterative allocator really walks over all addresses
+// in all pools in specified subnet. It also must not pick the same address twice
+// unless it runs out of pool space and must start over.
+TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) {
+ NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator();
+
+ // Let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already.
+ for (int i = 2; i < 10; ++i) {
+ stringstream min, max;
+
+ min << "192.0.2." << i * 10 + 1;
+ max << "192.0.2." << i * 10 + 9;
+
+ Pool4Ptr pool(new Pool4(IOAddress(min.str()),
+ IOAddress(max.str())));
+ // cout << "Adding pool: " << min.str() << "-" << max.str() << endl;
+ subnet_->addPool(pool);
+ }
+
+ int total = 10 + 8 * 9; // first pool (.100 - .109) has 10 addresses in it,
+ // there are 8 extra pools with 9 addresses in each.
+
+ // Let's keep picked addresses here and check their uniqueness.
+ std::set<IOAddress> generated_addrs;
+ int cnt = 0;
+ while (++cnt) {
+ IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0"));
+ EXPECT_TRUE(subnet_->inPool(candidate));
+
+ // One way to easily verify that the iterative allocator really works is
+ // to uncomment the following line and observe its output that it
+ // covers all defined subnets.
+ // cout << candidate.toText() << endl;
+
+ if (generated_addrs.find(candidate) == generated_addrs.end()) {
+ // We haven't had this
+ generated_addrs.insert(candidate);
+ } else {
+ // we have seen this address before. That should mean that we
+ // iterated over all addresses.
+ if (generated_addrs.size() == total) {
+ // we have exactly the number of address in all pools
+ break;
+ }
+ ADD_FAILURE() << "Too many or not enough unique addresses generated.";
+ break;
+ }
+
+ if ( cnt>total ) {
+ ADD_FAILURE() << "Too many unique addresses generated.";
+ break;
+ }
+ }
+
+ delete alloc;
+}
+
+
+// This test checks if really small pools are working
+TEST_F(AllocEngine4Test, smallPool4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ false);
+
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+
+ EXPECT_EQ("192.0.2.17", lease->addr_.toText());
+
+ // Do all checks on the lease
+ checkLease4(lease);
+
+ // Check that the lease is indeed in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if all addresses in a pool are currently used, the attempt
+// to find out a new lease fails.
+TEST_F(AllocEngine4Test, outOfAddresses4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.17");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL);
+ Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2),
+ 501, 502, 503, now, subnet_->getID()));
+ lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // There is just a single address in the pool and allocated it to someone
+ // else, so the allocation should fail
+
+ EXPECT_THROW(engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),false),
+ AllocFailed);
+}
+
+// This test checks if an expired lease can be reused in DISCOVER (fake allocation)
+TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.15");
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration
+
+ // Create configuration similar to other tests, but with a single address pool
+ subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3));
+ pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address
+ subnet_->addPool(pool_);
+ cfg_mgr.addSubnet4(subnet_);
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // CASE 1: Asking for any address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
+ checkLease4(lease);
+
+ // CASE 2: Asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()),
+ true);
+ // Check that we got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+}
+
+// This test checks if an expired lease can be reused in REQUEST (actual allocation)
+TEST_F(AllocEngine4Test, requestReuseExpiredLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.105");
+
+ // Just a different hw/client-id for the second client
+ uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ time_t now = time(NULL) - 500; // Allocated 500 seconds ago
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2),
+ 495, 100, 200, now, subnet_->getID()));
+ // lease was assigned 500 seconds ago, but its valid lifetime is 495, so it
+ // is expired already
+ ASSERT_TRUE(lease->expired());
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // A client comes along, asking specifically for this address
+ lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_,
+ IOAddress(addr.toText()), false);
+
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
+// This test checks if a lease is really renewed when renewLease4 method is
+// called
+TEST_F(AllocEngine4Test, renewLease4) {
+ boost::scoped_ptr<AllocEngine> engine;
+ ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+ ASSERT_TRUE(engine);
+
+ IOAddress addr("192.0.2.102");
+ const uint32_t old_lifetime = 100;
+ const uint32_t old_t1 = 50;
+ const uint32_t old_t2 = 75;
+ const time_t old_timestamp = time(NULL) - 45; // Allocated 45 seconds ago
+
+ // Just a different hw/client-id for the second client
+ const uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe};
+ const uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 };
+ Lease4Ptr lease(new Lease4(addr, clientid2, sizeof(clientid2), hwaddr2,
+ sizeof(hwaddr2), old_lifetime, old_t1, old_t2,
+ old_timestamp, subnet_->getID()));
+ ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+ // Lease was assigned 45 seconds ago and is valid for 100 seconds. Let's
+ // renew it.
+ ASSERT_FALSE(lease->expired());
+ lease = engine->renewLease4(subnet_, clientid_, hwaddr_, lease, false);
+ // Check that he got that single lease
+ ASSERT_TRUE(lease);
+ EXPECT_EQ(addr.toText(), lease->addr_.toText());
+
+ // Check that the lease matches subnet_, hwaddr_,clientid_ parameters
+ checkLease4(lease);
+
+ // Check that the lease is indeed updated in LeaseMgr
+ Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr);
+ ASSERT_TRUE(from_mgr);
+
+ // Now check that the lease in LeaseMgr has the same parameters
+ detailCompareLease(lease, from_mgr);
+}
+
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
index 7b23bcf..9b3d61b 100644
--- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -39,13 +39,180 @@ namespace {
class CfgMgrTest : public ::testing::Test {
public:
CfgMgrTest() {
+ // make sure we start with a clean configuration
+ CfgMgr::instance().deleteSubnets4();
+ CfgMgr::instance().deleteSubnets6();
}
~CfgMgrTest() {
+ // clean up after the test
+ CfgMgr::instance().deleteSubnets4();
CfgMgr::instance().deleteSubnets6();
+ CfgMgr::instance().deleteOptionDefs();
}
};
+// This test verifies that multiple option definitions can be added
+// under different option spaces.
+TEST_F(CfgMgrTest, getOptionDefs) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs1 = cfg_mgr.getOptionDefs("isc");
+ ASSERT_TRUE(option_defs1);
+ ASSERT_EQ(10, option_defs1->size());
+
+ // Iterate over all option definitions and check that they have
+ // valid codes. Also, their order should be the same as they
+ // were added (codes 100-109).
+ uint16_t code = 100;
+ for (OptionDefContainer::const_iterator it = option_defs1->begin();
+ it != option_defs1->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Sanity check that all 10 option definitions are there.
+ OptionDefContainerPtr option_defs2 = cfg_mgr.getOptionDefs("abcde");
+ ASSERT_TRUE(option_defs2);
+ ASSERT_EQ(10, option_defs2->size());
+
+ // Check that the option codes are valid.
+ code = 105;
+ for (OptionDefContainer::const_iterator it = option_defs2->begin();
+ it != option_defs2->end(); ++it, ++code) {
+ OptionDefinitionPtr def(*it);
+ ASSERT_TRUE(def);
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Let's make one more check that the empty set is returned when
+ // invalid option space is used.
+ OptionDefContainerPtr option_defs3 = cfg_mgr.getOptionDefs("non-existing");
+ ASSERT_TRUE(option_defs3);
+ EXPECT_TRUE(option_defs3->empty());
+}
+
+// This test verifies that single option definition is correctly
+// returned with getOptionDef function.
+TEST_F(CfgMgrTest, getOptionDef) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // Create a set of option definitions with codes between 100 and 109.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream option_name;
+ // Option name is unique, e.g. option-100, option-101 etc.
+ option_name << "option-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ // Add option definition to "isc" option space.
+ // Option codes are not duplicated so expect no error
+ // when adding them.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ }
+
+ // Create a set of option definitions with codes between 105 and 114 and
+ // add them to the different option space.
+ for (uint16_t code = 105; code < 115; ++code) {
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ OptionDefinitionPtr def(new OptionDefinition(option_name.str(), code,
+ "uint16"));
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "abcde"));
+ }
+
+ // Try to get option definitions one by one using all codes
+ // that we expect to be there.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("isc", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that the option codes are valid.
+ for (uint16_t code = 105; code < 115; ++code) {
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("abcde", code);
+ ASSERT_TRUE(def);
+ // Check that the option name is in the format of 'option-other-[code]'.
+ // That way we make sure that the options that have the same codes
+ // within different option spaces are different.
+ std::ostringstream option_name;
+ option_name << "option-other-" << code;
+ EXPECT_EQ(option_name.str(), def->getName());
+
+ EXPECT_EQ(code, def->getCode());
+ }
+
+ // Check that an option definition can be added to the standard
+ // (dhcp4 and dhcp6) option spaces when the option code is not
+ // reserved by the standard option.
+ OptionDefinitionPtr def6(new OptionDefinition("option-foo", 79, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def6, "dhcp6"));
+
+ OptionDefinitionPtr def4(new OptionDefinition("option-foo", 222, "uint16"));
+ EXPECT_NO_THROW(cfg_mgr.addOptionDef(def4, "dhcp4"));
+
+ // Try to query the option definition from an non-existing
+ // option space and expect NULL pointer.
+ OptionDefinitionPtr def = cfg_mgr.getOptionDef("non-existing", 56);
+ EXPECT_FALSE(def);
+
+ // Try to get the non-existing option definition from an
+ // existing option space.
+ EXPECT_FALSE(cfg_mgr.getOptionDef("isc", 56));
+
+}
+
+// This test verifies that the function that adds new option definition
+// throws exceptions when arguments are invalid.
+TEST_F(CfgMgrTest, addOptionDefNegative) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+ // The option code 65 is reserved for standard options either in
+ // DHCPv4 or DHCPv6. Thus we expect that adding an option to this
+ // option space fails.
+ OptionDefinitionPtr def(new OptionDefinition("option-foo", 65, "uint16"));
+
+ // Try reserved option space names.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp4"), isc::BadValue);
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, "dhcp6"), isc::BadValue);
+ // Try empty option space name.
+ ASSERT_THROW(cfg_mgr.addOptionDef(def, ""), isc::BadValue);
+ // Try NULL option definition.
+ ASSERT_THROW(cfg_mgr.addOptionDef(OptionDefinitionPtr(), "isc"),
+ isc::dhcp::MalformedOptionDefinition);
+ // Try adding option definition twice and make sure that it
+ // fails on the second attempt.
+ ASSERT_NO_THROW(cfg_mgr.addOptionDef(def, "isc"));
+ EXPECT_THROW(cfg_mgr.addOptionDef(def, "isc"), DuplicateOptionDefinition);
+}
// This test verifies if the configuration manager is able to hold and return
// valid leases
@@ -56,8 +223,8 @@ TEST_F(CfgMgrTest, subnet4) {
Subnet4Ptr subnet2(new Subnet4(IOAddress("192.0.2.64"), 26, 1, 2, 3));
Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.2.128"), 26, 1, 2, 3));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.0")));
cfg_mgr.addSubnet4(subnet1);
@@ -74,12 +241,17 @@ TEST_F(CfgMgrTest, subnet4) {
EXPECT_EQ(subnet2, cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
// Try to find an address that does not belong to any subnet
- EXPECT_EQ(Subnet4Ptr(), cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.192")));
+
+ // Check that deletion of the subnets works.
+ cfg_mgr.deleteSubnets4();
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.191")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.15")));
+ EXPECT_FALSE(cfg_mgr.getSubnet4(IOAddress("192.0.2.85")));
}
// This test verifies if the configuration manager is able to hold and return
// valid leases
-
TEST_F(CfgMgrTest, subnet6) {
CfgMgr& cfg_mgr = CfgMgr::instance();
@@ -87,8 +259,8 @@ TEST_F(CfgMgrTest, subnet6) {
Subnet6Ptr subnet2(new Subnet6(IOAddress("3000::"), 48, 1, 2, 3, 4));
Subnet6Ptr subnet3(new Subnet6(IOAddress("4000::"), 48, 1, 2, 3, 4));
- // there shouldn't be any subnet configured at this stage
- EXPECT_EQ( Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("2000::1")));
+ // There shouldn't be any subnet configured at this stage
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("2000::1")));
cfg_mgr.addSubnet6(subnet1);
@@ -104,12 +276,83 @@ TEST_F(CfgMgrTest, subnet6) {
EXPECT_EQ(subnet3, cfg_mgr.getSubnet6(IOAddress("4000::123")));
EXPECT_EQ(subnet2, cfg_mgr.getSubnet6(IOAddress("3000::dead:beef")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("5000::1")));
+ // Check that deletion of the subnets works.
cfg_mgr.deleteSubnets6();
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("200::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("3000::123")));
- EXPECT_EQ(Subnet6Ptr(), cfg_mgr.getSubnet6(IOAddress("4000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("200::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("3000::123")));
+ EXPECT_FALSE(cfg_mgr.getSubnet6(IOAddress("4000::123")));
}
+// This test verifies that new DHCPv4 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace4) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace4(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces4();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace4(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decode if a duplicate vendor space is allowed.
+}
+
+// This test verifies that new DHCPv6 option spaces can be added to
+// the configuration manager and that duplicated option space is
+// rejected.
+TEST_F(CfgMgrTest, optionSpace6) {
+ CfgMgr& cfg_mgr = CfgMgr::instance();
+
+ // Create some option spaces.
+ OptionSpacePtr space1(new OptionSpace("isc", false));
+ OptionSpacePtr space2(new OptionSpace("xyz", true));
+
+ // Add option spaces with different names and expect they
+ // are accepted.
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space1));
+ ASSERT_NO_THROW(cfg_mgr.addOptionSpace6(space2));
+
+ // Validate that the option spaces have been added correctly.
+ const OptionSpaceCollection& spaces = cfg_mgr.getOptionSpaces6();
+
+ ASSERT_EQ(2, spaces.size());
+ EXPECT_FALSE(spaces.find("isc") == spaces.end());
+ EXPECT_FALSE(spaces.find("xyz") == spaces.end());
+
+ // Create another option space with the name that duplicates
+ // the existing option space.
+ OptionSpacePtr space3(new OptionSpace("isc", true));
+ // Expect that the duplicate option space is rejected.
+ ASSERT_THROW(
+ cfg_mgr.addOptionSpace6(space3), isc::dhcp::InvalidOptionSpace
+ );
+
+ // @todo decide if a duplicate vendor space is allowed.
+}
+
+// No specific tests for getSubnet6. That method (2 overloaded versions) is tested
+// in Dhcpv6SrvTest.selectSubnetAddr and Dhcpv6SrvTest.selectSubnetIface
+// (see src/bin/dhcp6/tests/dhcp6_srv_unittest.cc)
+
} // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_factory_unittest.cc
index e343c44..9924476 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,136 @@ public:
}
};
-// This test checks if the LeaseMgr can be instantiated and that it
-// parses parameters string properly.
+// This test checks that a database access string can be parsed correctly.
TEST_F(LeaseMgrFactoryTest, parse) {
- std::map<std::string, std::string> parameters = LeaseMgrFactory::parse(
- "param1=value1 param2=value2 param3=value3");
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(
+ "user=me password=forbidden name=kea somethingelse= type=mysql");
- EXPECT_EQ("value1", parameters["param1"]);
- EXPECT_EQ("value2", parameters["param2"]);
- EXPECT_TRUE(parameters.find("type") == parameters.end());
+ EXPECT_EQ(5, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+ EXPECT_EQ("", parameters["somethingelse"]);
+}
+
+// This test checks that an invalid database access string behaves as expected.
+TEST_F(LeaseMgrFactoryTest, parseInvalid) {
+
+ // No tokens in the string, so we expect no parameters
+ std::string invalid = "";
+ LeaseMgr::ParameterMap parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(0, parameters.size());
+
+ // With spaces, there are some tokens so we expect invalid parameter
+ // as there are no equals signs.
+ invalid = " \t ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ invalid = " noequalshere ";
+ EXPECT_THROW(LeaseMgrFactory::parse(invalid), isc::InvalidParameter);
+
+ // A single "=" is valid string, but is placed here as the result is
+ // expected to be nothing.
+ invalid = "=";
+ parameters = LeaseMgrFactory::parse(invalid);
+ EXPECT_EQ(1, parameters.size());
+ EXPECT_EQ("", parameters[""]);
+}
+
+/// @brief redactConfigString test
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks.
+TEST_F(LeaseMgrFactoryTest, redactAccessString) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me password=forbidden name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("forbidden", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - empty password
+///
+/// Checks that the redacted configuration string includes the password only
+/// as a set of asterisks, even if the password is null.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringEmptyPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql password=");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // ... and again to check that the position of the empty password in the
+ // string does not matter.
+ parameters = LeaseMgrFactory::parse("user=me password= name=kea type=mysql");
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(4, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("*****", parameters["password"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+}
+
+/// @brief redactConfigString test - no password
+///
+/// Checks that the redacted configuration string excludes the password if there
+/// was no password to begion with.
+TEST_F(LeaseMgrFactoryTest, redactAccessStringNoPassword) {
+
+ LeaseMgr::ParameterMap parameters =
+ LeaseMgrFactory::parse("user=me name=kea type=mysql");
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
+
+ // Redact the result. To check, break the redacted string down into its
+ // components.
+ std::string redacted = LeaseMgrFactory::redactedAccessString(parameters);
+ parameters = LeaseMgrFactory::parse(redacted);
+
+ EXPECT_EQ(3, parameters.size());
+ EXPECT_EQ("me", parameters["user"]);
+ EXPECT_EQ("kea", parameters["name"]);
+ EXPECT_EQ("mysql", parameters["type"]);
}
}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
index 38c9555..8d8c7f8 100644
--- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc
@@ -272,7 +272,7 @@ TEST(Lease4, Lease4Constructor) {
// Create the lease
Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time,
SUBNET_ID);
EXPECT_EQ(ADDRESS[i], static_cast<uint32_t>(lease.addr_));
@@ -312,10 +312,10 @@ TEST(Lease4, OperatorEquals) {
// Check when the leases are equal.
Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR),
- CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time,
+ CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0,
SUBNET_ID);
EXPECT_TRUE(lease1 == lease2);
EXPECT_FALSE(lease1 != lease2);
diff --git a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
index 89cacef..ddb2645 100644
--- a/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
+++ b/src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
@@ -770,7 +770,9 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
}
// Get the leases matching the hardware address of lease 1
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ HWAddr tmp(leases[1]->hwaddr_, HTYPE_ETHER);
+ Lease4Collection returned = lmptr_->getLease4(tmp);
// Should be three leases, matching leases[1], [3] and [5].
ASSERT_EQ(3, returned.size());
@@ -787,13 +789,15 @@ TEST_F(MySqlLeaseMgrTest, getLease4Hwaddr) {
EXPECT_EQ(straddress4_[5], addresses[2]);
// Repeat test with just one expected match
- returned = lmptr_->getLease4(leases[2]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[2]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(1, returned.size());
detailCompareLease(leases[2], *returned.begin());
// Check that an empty vector is valid
EXPECT_TRUE(leases[7]->hwaddr_.empty());
- returned = lmptr_->getLease4(leases[7]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[7]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(1, returned.size());
detailCompareLease(leases[7], *returned.begin());
@@ -816,7 +820,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
ASSERT_EQ(1, returned.size());
detailCompareLease(leases[1], *returned.begin());
(void) lmptr_->deleteLease(leases[1]->addr_);
@@ -831,7 +836,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSize) {
// be any indication in the C API.
leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Collection returned = lmptr_->getLease4(leases[1]->hwaddr_);
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Collection returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER));
EXPECT_EQ(0, returned.size());
}
@@ -848,27 +854,31 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
// Get the leases matching the hardware address of lease 1 and
// subnet ID of lease 1. Result should be a single lease - lease 1.
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
// Try for a match to the hardware address of lease 1 and the wrong
// subnet ID.
- returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
// Try for a match to the subnet ID of lease 1 (and lease 4) but
// the wrong hardware address.
vector<uint8_t> invalid_hwaddr(15, 0x77);
- returned = lmptr_->getLease4(invalid_hwaddr,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_);
EXPECT_FALSE(returned);
// Try for a match to an unknown hardware address and an unknown
// subnet ID.
- returned = lmptr_->getLease4(invalid_hwaddr,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ returned = lmptr_->getLease4(HWAddr(invalid_hwaddr, HTYPE_ETHER),
leases[1]->subnet_id_ + 1);
EXPECT_FALSE(returned);
@@ -880,7 +890,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetId) {
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_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ EXPECT_THROW(returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_),
isc::dhcp::MultipleRecords);
@@ -905,7 +916,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
for (uint8_t i = 0; i <= Lease4::HWADDR_MAX; ++i) {
leases[1]->hwaddr_.resize(i, i);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
ASSERT_TRUE(returned);
detailCompareLease(leases[1], returned);
@@ -919,7 +931,8 @@ TEST_F(MySqlLeaseMgrTest, getLease4HwaddrSubnetIdSize) {
// be any indication in the C API.
leases[1]->hwaddr_.resize(Lease4::HWADDR_MAX + 100, 42);
EXPECT_TRUE(lmptr_->addLease(leases[1]));
- Lease4Ptr returned = lmptr_->getLease4(leases[1]->hwaddr_,
+ // @todo: Simply use HWAddr directly once 2589 is implemented
+ Lease4Ptr returned = lmptr_->getLease4(HWAddr(leases[1]->hwaddr_, HTYPE_ETHER),
leases[1]->subnet_id_);
EXPECT_FALSE(returned);
}
diff --git a/src/lib/dhcpsrv/tests/option_space_unittest.cc b/src/lib/dhcpsrv/tests/option_space_unittest.cc
new file mode 100644
index 0000000..f8d75c8
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/option_space_unittest.cc
@@ -0,0 +1,150 @@
+// Copyright (C) 2012, 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <dhcpsrv/option_space.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::dhcp;
+using namespace isc;
+
+namespace {
+
+// The purpose of this test is to verify that the constructor
+// creates an object with members initialized to correct values.
+TEST(OptionSpaceTest, constructor) {
+ // Create some option space.
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Create another object with different values
+ // to check that the values will change.
+ OptionSpace space2("abc", false);
+ EXPECT_EQ("abc", space2.getName());
+ EXPECT_FALSE(space2.isVendorSpace());
+
+ // Verify that constructor throws exception if invalid
+ // option space name is provided.
+ EXPECT_THROW(OptionSpace("invalid%space.name"), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify that the vendor-space flag
+// can be overriden.
+TEST(OptionSpaceTest, setVendorSpace) {
+ OptionSpace space("isc", true);
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_TRUE(space.isVendorSpace());
+
+ // Override the vendor space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+// The purpose of this test is to verify that the static function
+// to validate the option space name works correctly.
+TEST(OptionSpaceTest, validateName) {
+ // Positive test scenarios: letters, digits, dashes, underscores
+ // lower/upper case allowed.
+ EXPECT_TRUE(OptionSpace::validateName("abc"));
+ EXPECT_TRUE(OptionSpace::validateName("dash-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("two-dashes-allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("underscore_three_times_allowed"));
+ EXPECT_TRUE(OptionSpace::validateName("digits0912"));
+ EXPECT_TRUE(OptionSpace::validateName("1234"));
+ EXPECT_TRUE(OptionSpace::validateName("UPPER_CASE_allowed"));
+
+ // Negative test scenarions: empty strings, dots, spaces are not
+ // allowed
+ EXPECT_FALSE(OptionSpace::validateName(""));
+ EXPECT_FALSE(OptionSpace::validateName(" "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc "));
+ EXPECT_FALSE(OptionSpace::validateName("isc "));
+ EXPECT_FALSE(OptionSpace::validateName(" isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc with-space"));
+
+ // Hyphens and underscores are not allowed at the beginning
+ // and at the end of the option space name.
+ EXPECT_FALSE(OptionSpace::validateName("-isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc-"));
+ EXPECT_FALSE(OptionSpace::validateName("_isc"));
+ EXPECT_FALSE(OptionSpace::validateName("isc_"));
+
+ // Test other special characters
+ const char specials[] = { '!', '@', '#', '$', '%', '^', '&', '*', '(', ')',
+ '+', '=', '[', ']', '{', '}', ';', ':', '"', '\'',
+ '\\', '|', '<','>', ',', '.', '?', '~', '`' };
+ for (int i = 0; i < sizeof(specials); ++i) {
+ std::ostringstream stream;
+ // Concatenate valid option space name: "abc" with an invalid character.
+ // That way we get option space names like: "abc!", "abc$" etc. It is
+ // expected that the validating function fails form them.
+ stream << "abc" << specials[i];
+ EXPECT_FALSE(OptionSpace::validateName(stream.str()))
+ << "Test failed for special character '" << specials[i] << "'.";
+ }
+}
+
+// The purpose of this test is to verify that the constructors of the
+// OptionSpace6 class set the class members to correct values.
+TEST(OptionSpace6Test, constructor) {
+ // Create some option space and do not specify enterprise number.
+ // In such case the vendor space flag is expected to be
+ // set to false.
+ OptionSpace6 space1("abcd");
+ EXPECT_EQ("abcd", space1.getName());
+ EXPECT_FALSE(space1.isVendorSpace());
+
+ // Create an option space and specify an enterprise number. In this
+ // case the vendor space flag is expected to be set to true and the
+ // enterprise number should be set to a desired value.
+ OptionSpace6 space2("abcd", 2145);
+ EXPECT_EQ("abcd", space2.getName());
+ EXPECT_TRUE(space2.isVendorSpace());
+ EXPECT_EQ(2145, space2.getEnterpriseNumber());
+
+ // Verify that constructors throw an exception when invalid option
+ // space name has been specified.
+ EXPECT_THROW(OptionSpace6("isc dhcp"), InvalidOptionSpace);
+ EXPECT_THROW(OptionSpace6("isc%dhcp", 2145), InvalidOptionSpace);
+}
+
+// The purpose of this test is to verify an option space can be marked
+// vendor option space and enterprise number can be set.
+TEST(OptionSpace6Test, setVendorSpace) {
+ OptionSpace6 space("isc");
+ EXPECT_EQ("isc", space.getName());
+ EXPECT_FALSE(space.isVendorSpace());
+
+ // Mark it vendor option space and set enterprise id.
+ space.setVendorSpace(1234);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(1234, space.getEnterpriseNumber());
+
+ // Override the enterprise number to make sure and make sure that
+ // the new number is returned by the object.
+ space.setVendorSpace(2345);
+ EXPECT_TRUE(space.isVendorSpace());
+ EXPECT_EQ(2345, space.getEnterpriseNumber());
+
+ // Clear the vendor option space flag.
+ space.clearVendorSpace();
+ EXPECT_FALSE(space.isVendorSpace());
+}
+
+
+}; // end of anonymous namespace
diff --git a/src/lib/dhcpsrv/tests/subnet_unittest.cc b/src/lib/dhcpsrv/tests/subnet_unittest.cc
index 9ebef9c..9f06c89 100644
--- a/src/lib/dhcpsrv/tests/subnet_unittest.cc
+++ b/src/lib/dhcpsrv/tests/subnet_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2013 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -61,28 +61,28 @@ TEST(Subnet4Test, Pool4InSubnet4) {
Subnet4Ptr subnet(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3));
- Pool4Ptr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
- Pool4Ptr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
- Pool4Ptr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
+ PoolPtr pool1(new Pool4(IOAddress("192.1.2.0"), 25));
+ PoolPtr pool2(new Pool4(IOAddress("192.1.2.128"), 26));
+ PoolPtr pool3(new Pool4(IOAddress("192.1.2.192"), 30));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool4Ptr mypool = subnet->getPool4();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool4(pool2);
- subnet->addPool4(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool4();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool4(IOAddress("192.1.2.195"));
+ mypool = subnet->getPool(IOAddress("192.1.2.195"));
EXPECT_EQ(mypool, pool3);
@@ -94,16 +94,16 @@ TEST(Subnet4Test, Subnet4_Pool4_checks) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.255.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool4Ptr pool2(new Pool4(IOAddress("193.0.0.0"), 24));
- EXPECT_THROW(subnet->addPool4(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool4Ptr pool3(new Pool4(IOAddress("1.2.3.4"), 16));
- EXPECT_THROW(subnet->addPool4(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
}
TEST(Subnet4Test, addInvalidOption) {
@@ -115,13 +115,15 @@ TEST(Subnet4Test, addInvalidOption) {
// Create option with invalid universe (V6 instead of V4).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp4"),
+ isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp4"),
+ isc::BadValue);
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -130,7 +132,7 @@ TEST(Subnet4Test, inRangeinPool) {
// this one is in subnet
Pool4Ptr pool1(new Pool4(IOAddress("192.2.0.0"), 16));
- subnet->addPool4(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("192.1.1.1")));
@@ -205,28 +207,28 @@ TEST(Subnet6Test, Pool6InSubnet6) {
Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4));
- Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
- Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
+ PoolPtr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
+ PoolPtr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:2::"), 64));
+ PoolPtr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:3::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// If there's only one pool, get that pool
- Pool6Ptr mypool = subnet->getPool6();
+ PoolPtr mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
- subnet->addPool6(pool2);
- subnet->addPool6(pool3);
+ subnet->addPool(pool2);
+ subnet->addPool(pool3);
// If there are more than one pool and we didn't provide hint, we
// should get the first pool
- mypool = subnet->getPool6();
+ mypool = subnet->getPool();
EXPECT_EQ(mypool, pool1);
// If we provide a hint, we should get a pool that this hint belongs to
- mypool = subnet->getPool6(IOAddress("2001:db8:1:3::dead:beef"));
+ mypool = subnet->getPool(IOAddress("2001:db8:1:3::dead:beef"));
EXPECT_EQ(mypool, pool3);
}
@@ -237,21 +239,21 @@ TEST(Subnet6Test, Subnet6_Pool6_checks) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8:1:1::"), 64));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// this one is larger than the subnet!
Pool6Ptr pool2(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::"), 48));
- EXPECT_THROW(subnet->addPool6(pool2), BadValue);
+ EXPECT_THROW(subnet->addPool(pool2), BadValue);
// this one is totally out of blue
Pool6Ptr pool3(new Pool6(Pool6::TYPE_IA, IOAddress("3000::"), 16));
- EXPECT_THROW(subnet->addPool6(pool3), BadValue);
+ EXPECT_THROW(subnet->addPool(pool3), BadValue);
Pool6Ptr pool4(new Pool6(Pool6::TYPE_IA, IOAddress("4001:db8:1::"), 80));
- EXPECT_THROW(subnet->addPool6(pool4), BadValue);
+ EXPECT_THROW(subnet->addPool(pool4), BadValue);
}
TEST(Subnet6Test, addOptions) {
@@ -261,26 +263,59 @@ TEST(Subnet6Test, addOptions) {
// Differentiate options by their codes (100-109)
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Add 7 options to another option space. The option codes partially overlap
+ // with option codes that we have added to dhcp6 option space.
+ for (uint16_t code = 105; code < 112; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "isc"));
}
// Get options from the Subnet and check if all 10 are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(10, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(10, options->size());
- // Validate codes of added options.
+ // Validate codes of options added to dhcp6 option space.
uint16_t expected_code = 100;
- for (Subnet::OptionContainer::const_iterator option_desc = options.begin();
- option_desc != options.end(); ++option_desc) {
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
ASSERT_TRUE(option_desc->option);
EXPECT_EQ(expected_code, option_desc->option->getType());
++expected_code;
}
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ ASSERT_EQ(7, options->size());
+
+ // Validate codes of options added to isc option space.
+ expected_code = 105;
+ for (Subnet::OptionContainer::const_iterator option_desc = options->begin();
+ option_desc != options->end(); ++option_desc) {
+ ASSERT_TRUE(option_desc->option);
+ EXPECT_EQ(expected_code, option_desc->option->getType());
+ ++expected_code;
+ }
+
+ // Try to get options from a non-existing option space.
+ options = subnet->getOptionDescriptors("abcd");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ // Delete options from all spaces.
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ // Make sure that all options have been removed.
+ options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
+
+ options = subnet->getOptionDescriptors("isc");
+ ASSERT_TRUE(options);
+ EXPECT_TRUE(options->empty());
}
TEST(Subnet6Test, addNonUniqueOptions) {
@@ -292,19 +327,19 @@ TEST(Subnet6Test, addNonUniqueOptions) {
// In the inner loop we create options with unique codes (100-109).
for (uint16_t code = 100; code < 110; ++code) {
OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
- ASSERT_NO_THROW(subnet->addOption(option));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
}
}
// Sanity check that all options are there.
- Subnet::OptionContainer options = subnet->getOptions();
- ASSERT_EQ(20, options.size());
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
+ ASSERT_EQ(20, options->size());
// Use container index #1 to get the options by their codes.
- Subnet::OptionContainerTypeIndex& idx = options.get<1>();
+ Subnet::OptionContainerTypeIndex& idx = options->get<1>();
// Look for the codes 100-109.
for (uint16_t code = 100; code < 110; ++ code) {
- // For each code we should get two instances of options.
+ // For each code we should get two instances of options->
std::pair<Subnet::OptionContainerTypeIndex::const_iterator,
Subnet::OptionContainerTypeIndex::const_iterator> range =
idx.equal_range(code);
@@ -329,8 +364,8 @@ TEST(Subnet6Test, addNonUniqueOptions) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
}
TEST(Subnet6Test, addInvalidOption) {
@@ -342,13 +377,13 @@ TEST(Subnet6Test, addInvalidOption) {
// Create option with invalid universe (V4 instead of V6).
// Attempt to add this option should result in exception.
OptionPtr option1(new Option(Option::V4, code, OptionBuffer(10, 0xFF)));
- EXPECT_THROW(subnet->addOption(option1), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option1, false, "dhcp6"), isc::BadValue);
// Create NULL pointer option. Attempt to add NULL option
// should result in exception.
OptionPtr option2;
ASSERT_FALSE(option2);
- EXPECT_THROW(subnet->addOption(option2), isc::BadValue);
+ EXPECT_THROW(subnet->addOption(option2, false, "dhcp6"), isc::BadValue);
}
TEST(Subnet6Test, addPersistentOption) {
@@ -367,24 +402,24 @@ TEST(Subnet6Test, addPersistentOption) {
// and options with these codes will be flagged non-persistent.
// Options with other codes will be flagged persistent.
bool persistent = (code % 3) ? true : false;
- ASSERT_NO_THROW(subnet->addOption(option, persistent));
+ ASSERT_NO_THROW(subnet->addOption(option, persistent, "dhcp6"));
}
// Get added options from the subnet.
- Subnet::OptionContainer options = subnet->getOptions();
+ Subnet::OptionContainerPtr options = subnet->getOptionDescriptors("dhcp6");
- // options.get<2> returns reference to container index #2. This
+ // options->get<2> returns reference to container index #2. This
// index is used to access options by the 'persistent' flag.
- Subnet::OptionContainerPersistIndex& idx = options.get<2>();
+ Subnet::OptionContainerPersistIndex& idx = options->get<2>();
- // Get all persistent options.
+ // Get all persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_persistent =
idx.equal_range(true);
// 3 out of 10 options have been flagged persistent.
ASSERT_EQ(7, distance(range_persistent.first, range_persistent.second));
- // Get all non-persistent options.
+ // Get all non-persistent options->
std::pair<Subnet::OptionContainerPersistIndex::const_iterator,
Subnet::OptionContainerPersistIndex::const_iterator> range_non_persistent =
idx.equal_range(false);
@@ -393,8 +428,33 @@ TEST(Subnet6Test, addPersistentOption) {
subnet->delOptions();
- options = subnet->getOptions();
- EXPECT_EQ(0, options.size());
+ options = subnet->getOptionDescriptors("dhcp6");
+ EXPECT_EQ(0, options->size());
+}
+
+TEST(Subnet6Test, getOptionDescriptor) {
+ Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8::"), 56, 1, 2, 3, 4));
+
+ // Add 10 options to a "dhcp6" option space in the subnet.
+ for (uint16_t code = 100; code < 110; ++code) {
+ OptionPtr option(new Option(Option::V6, code, OptionBuffer(10, 0xFF)));
+ ASSERT_NO_THROW(subnet->addOption(option, false, "dhcp6"));
+ }
+
+ // Check that we can get each added option descriptor using
+ // individually.
+ for (uint16_t code = 100; code < 110; ++code) {
+ std::ostringstream stream;
+ // First, try the invalid option space name.
+ Subnet::OptionDescriptor desc = subnet->getOptionDescriptor("isc", code);
+ // Returned descriptor should contain NULL option ptr.
+ EXPECT_FALSE(desc.option);
+ // Now, try the valid option space.
+ desc = subnet->getOptionDescriptor("dhcp6", code);
+ // Test that the option code matches the expected code.
+ ASSERT_TRUE(desc.option);
+ EXPECT_EQ(code, desc.option->getType());
+ }
}
// This test verifies that inRange() and inPool() methods work properly.
@@ -404,7 +464,7 @@ TEST(Subnet6Test, inRangeinPool) {
// this one is in subnet
Pool6Ptr pool1(new Pool6(Pool6::TYPE_IA, IOAddress("2001:db8::10"),
IOAddress("2001:db8::20")));
- subnet->addPool6(pool1);
+ subnet->addPool(pool1);
// 192.1.1.1 belongs to the subnet...
EXPECT_TRUE(subnet->inRange(IOAddress("2001:db8::1")));
@@ -445,4 +505,15 @@ TEST(Subnet6Test, get) {
EXPECT_EQ(32, subnet.get().second);
}
+// This trivial test checks if interface name is stored properly
+// in Subnet6 objects.
+TEST(Subnet6Test, iface) {
+ Subnet6 subnet(IOAddress("2001:db8::"), 32, 1, 2, 3, 4);
+
+ EXPECT_TRUE(subnet.getIface().empty());
+
+ subnet.setIface("en1");
+ EXPECT_EQ("en1", subnet.getIface());
+}
+
};
diff --git a/src/lib/dhcpsrv/utils.h b/src/lib/dhcpsrv/utils.h
new file mode 100644
index 0000000..26d98ce
--- /dev/null
+++ b/src/lib/dhcpsrv/utils.h
@@ -0,0 +1,40 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DHCPSRV_UTILS_H
+#define DHCPSRV_UTILS_H
+
+#include <exceptions/exceptions.h>
+
+namespace isc {
+namespace dhcp {
+
+/// An exception that is thrown if a DHCPv6 protocol violation occurs while
+/// processing a message (e.g. a mandatory option is missing)
+class RFCViolation : public isc::Exception {
+public:
+
+/// @brief constructor
+///
+/// @param file name of the file, where exception occurred
+/// @param line line of the file, where exception occurred
+/// @param what text description of the issue that caused exception
+RFCViolation(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+}; // namespace isc::dhcp
+}; // namespace isc
+
+#endif // DHCPSRV_UTILS_H
diff --git a/src/lib/dns/Makefile.am b/src/lib/dns/Makefile.am
index 8525842..286bd8c 100644
--- a/src/lib/dns/Makefile.am
+++ b/src/lib/dns/Makefile.am
@@ -23,6 +23,7 @@ EXTRA_DIST += rdata/generic/cname_5.cc
EXTRA_DIST += rdata/generic/cname_5.h
EXTRA_DIST += rdata/generic/detail/char_string.cc
EXTRA_DIST += rdata/generic/detail/char_string.h
+EXTRA_DIST += rdata/generic/detail/lexer_util.h
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.cc
EXTRA_DIST += rdata/generic/detail/nsec_bitmap.h
EXTRA_DIST += rdata/generic/detail/nsec3param_common.cc
@@ -95,6 +96,7 @@ lib_LTLIBRARIES = libb10-dns++.la
libb10_dns___la_LDFLAGS = -no-undefined -version-info 2:0:0
libb10_dns___la_SOURCES =
+libb10_dns___la_SOURCES += dns_fwd.h
libb10_dns___la_SOURCES += edns.h edns.cc
libb10_dns___la_SOURCES += exceptions.h exceptions.cc
libb10_dns___la_SOURCES += master_lexer_inputsource.h master_lexer_inputsource.cc
@@ -124,9 +126,11 @@ libb10_dns___la_SOURCES += tsig.h tsig.cc
libb10_dns___la_SOURCES += tsigerror.h tsigerror.cc
libb10_dns___la_SOURCES += tsigkey.h tsigkey.cc
libb10_dns___la_SOURCES += tsigrecord.h tsigrecord.cc
-libb10_dns___la_SOURCES += character_string.h character_string.cc
libb10_dns___la_SOURCES += master_loader_callbacks.h master_loader_callbacks.cc
libb10_dns___la_SOURCES += master_loader.h
+libb10_dns___la_SOURCES += rrset_collection_base.h
+libb10_dns___la_SOURCES += rrset_collection.h rrset_collection.cc
+libb10_dns___la_SOURCES += zone_checker.h zone_checker.cc
libb10_dns___la_SOURCES += rdata/generic/detail/char_string.h
libb10_dns___la_SOURCES += rdata/generic/detail/char_string.cc
libb10_dns___la_SOURCES += rdata/generic/detail/nsec_bitmap.h
@@ -156,6 +160,7 @@ libdns___includedir = $(includedir)/$(PACKAGE_NAME)/dns
libdns___include_HEADERS = \
edns.h \
exceptions.h \
+ dns_fwd.h \
labelsequence.h \
message.h \
masterload.h \
@@ -170,8 +175,11 @@ libdns___include_HEADERS = \
rdata.h \
rrparamregistry.h \
rrset.h \
+ rrset_collection_base.h \
+ rrset_collection.h \
rrttl.h \
- tsigkey.h
+ tsigkey.h \
+ zone_checker.h
# Purposely not installing these headers:
# name_internal.h: used only internally, and not actually DNS specific
# rdata/*/detail/*.h: these are internal use only
diff --git a/src/lib/dns/character_string.cc b/src/lib/dns/character_string.cc
deleted file mode 100644
index 8b31948..0000000
--- a/src/lib/dns/character_string.cc
+++ /dev/null
@@ -1,145 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#include "character_string.h"
-#include "rdata.h"
-
-using namespace std;
-using namespace isc::dns::rdata;
-
-namespace isc {
-namespace dns {
-
-namespace {
-bool isDigit(char c) {
- return (('0' <= c) && (c <= '9'));
-}
-}
-
-std::string
-characterstr::getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool* quoted)
-{
- string result;
-
- // If the input string only contains white-spaces, it is an invalid
- // <character-string>
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText, "Invalid text format, \
- <character-string> field is missing.");
- }
-
- // Whether the <character-string> is separated with double quotes (")
- bool quotes_separated = (*input_iterator == '"');
- // Whether the quotes are pared if the string is quotes separated
- bool quotes_paired = false;
-
- if (quotes_separated) {
- ++input_iterator;
- }
-
- while(input_iterator < input_str.end()){
- // Escaped characters processing
- if (*input_iterator == '\\') {
- if (input_iterator + 1 == input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- if (isDigit(*(input_iterator + 1))) {
- // \DDD where each D is a digit. It its the octet
- // corresponding to the decimal number described by DDD
- if (input_iterator + 3 >= input_str.end()) {
- isc_throw(InvalidRdataText, "<character-string> ended \
- prematurely.");
- } else {
- int n = 0;
- ++input_iterator;
- for (int i = 0; i < 3; ++i) {
- if (isDigit(*input_iterator)) {
- n = n*10 + (*input_iterator - '0');
- ++input_iterator;
- } else {
- isc_throw(InvalidRdataText, "Illegal decimal \
- escaping series");
- }
- }
- if (n > 255) {
- isc_throw(InvalidRdataText, "Illegal octet \
- number");
- }
- result.push_back(n);
- continue;
- }
- } else {
- ++input_iterator;
- result.push_back(*input_iterator);
- ++input_iterator;
- continue;
- }
- }
- }
-
- if (quotes_separated) {
- // If the <character-string> is seperated with quotes symbol and
- // another quotes symbol is encountered, it is the end of the
- // <character-string>
- if (*input_iterator == '"') {
- quotes_paired = true;
- ++input_iterator;
- // Reach the end of character string
- break;
- }
- } else if (*input_iterator == ' ') {
- // If the <character-string> is not seperated with quotes symbol,
- // it is seperated with <space> char
- break;
- }
-
- result.push_back(*input_iterator);
-
- ++input_iterator;
- }
-
- if (result.size() > MAX_CHARSTRING_LEN) {
- isc_throw(CharStringTooLong, "<character-string> is too long");
- }
-
- if (quotes_separated && !quotes_paired) {
- isc_throw(InvalidRdataText, "The quotes are not paired");
- }
-
- if (quoted != NULL) {
- *quoted = quotes_separated;
- }
-
- return (result);
-}
-
-std::string
-characterstr::getNextCharacterString(util::InputBuffer& buffer, size_t len) {
- uint8_t str_len = buffer.readUint8();
-
- size_t pos = buffer.getPosition();
- if (len - pos < str_len) {
- isc_throw(InvalidRdataLength, "Invalid string length");
- }
-
- uint8_t buf[MAX_CHARSTRING_LEN];
- buffer.readData(buf, str_len);
- return (string(buf, buf + str_len));
-}
-
-} // end of namespace dns
-} // end of namespace isc
diff --git a/src/lib/dns/character_string.h b/src/lib/dns/character_string.h
deleted file mode 100644
index 0bfa38b..0000000
--- a/src/lib/dns/character_string.h
+++ /dev/null
@@ -1,60 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-#ifndef CHARACTER_STRING_H
-#define CHARACTER_STRING_H
-
-#include <string>
-#include <exceptions/exceptions.h>
-#include <util/buffer.h>
-
-namespace isc {
-namespace dns {
-
-// \brief Some utility functions to extract <character-string> from string
-// or InputBuffer
-//
-// <character-string> is expressed in one or two ways: as a contiguous set
-// of characters without interior spaces, or as a string beginning with a "
-// and ending with a ". Inside a " delimited string any character can
-// occur, except for a " itself, which must be quoted using \ (back slash).
-// Ref. RFC1035
-
-
-namespace characterstr {
- /// Get a <character-string> from a string
- ///
- /// \param input_str The input string
- /// \param input_iterator The iterator from which to start extracting,
- /// the iterator will be updated to new position after the function
- /// is returned
- /// \param quoted If not \c NULL, returns \c true at this address if
- /// the string is quoted, \cfalse otherwise
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool* quoted = NULL);
-
- /// Get a <character-string> from a input buffer
- ///
- /// \param buffer The input buffer
- /// \param len The input buffer total length
- /// \return A std::string that contains the extracted <character-string>
- std::string getNextCharacterString(util::InputBuffer& buffer, size_t len);
-
-} // namespace characterstr
-} // namespace dns
-} // namespace isc
-
-#endif // CHARACTER_STRING_H
diff --git a/src/lib/dns/dns_fwd.h b/src/lib/dns/dns_fwd.h
new file mode 100644
index 0000000..df71388
--- /dev/null
+++ b/src/lib/dns/dns_fwd.h
@@ -0,0 +1,64 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_FWD_H
+#define DNS_FWD_H 1
+
+/// \file dns_fwd.h
+/// \brief Forward declarations for definitions of libdns++
+///
+/// This file provides a set of forward declarations for definitions commonly
+/// used in libdns++ to help minimize dependency when actual the definition
+/// is not necessary.
+
+namespace isc {
+namespace dns {
+
+class EDNS;
+class Name;
+class MasterLoader;
+class MasterLoaderCallbacks;
+class Message;
+class AbstractMessageRenderer;
+class MessageRenderer;
+class NSEC3Hash;
+class NSEC3HashCreator;
+class Opcode;
+class Question;
+class Rcode;
+namespace rdata {
+class Rdata;
+}
+class RRCollator;
+class RRClass;
+class RRType;
+class RRTTL;
+class AbstractRRset;
+class RdataIterator;
+class RRsetCollectionBase;
+class RRsetCollection;
+class Serial;
+class TSIGContext;
+class TSIGError;
+class TSIGKey;
+class TSIGKeyRing;
+class TSIGRecord;
+
+} // namespace dns
+} // namespace isc
+#endif // DNS_FWD_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/gen-rdatacode.py.in b/src/lib/dns/gen-rdatacode.py.in
index 2bf9de6..fc63d73 100755
--- a/src/lib/dns/gen-rdatacode.py.in
+++ b/src/lib/dns/gen-rdatacode.py.in
@@ -32,8 +32,13 @@ import sys
#
# Example:
# new_rdata_factory_users = [('a', 'in'), ('a', 'ch'), ('soa', 'generic')]
-new_rdata_factory_users = [('aaaa', 'in'), ('txt', 'generic'),
- ('spf', 'generic')]
+new_rdata_factory_users = [('aaaa', 'in'),
+ ('hinfo', 'generic'),
+ ('naptr', 'generic'),
+ ('soa', 'generic'),
+ ('spf', 'generic'),
+ ('txt', 'generic')
+ ]
re_typecode = re.compile('([\da-z]+)_(\d+)')
classcode2txt = {}
diff --git a/src/lib/dns/master_lexer.cc b/src/lib/dns/master_lexer.cc
index e4ddedc..9563355 100644
--- a/src/lib/dns/master_lexer.cc
+++ b/src/lib/dns/master_lexer.cc
@@ -18,17 +18,28 @@
#include <dns/master_lexer_inputsource.h>
#include <dns/master_lexer_state.h>
+#include <boost/foreach.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/lexical_cast.hpp>
#include <bitset>
#include <cassert>
+#include <limits>
#include <string>
#include <vector>
namespace isc {
namespace dns {
+// The definition of SOURCE_SIZE_UNKNOWN. Note that we initialize it using
+// a method of another library. Technically, this could trigger a static
+// initialization fiasco. But in this particular usage it's very unlikely
+// to happen because this value is expected to be used only as a return
+// value of a MasterLexer's method, and its constructor needs definitions
+// here.
+const size_t MasterLexer::SOURCE_SIZE_UNKNOWN =
+ std::numeric_limits<size_t>::max();
+
namespace {
typedef boost::shared_ptr<master_lexer_internal::InputSource> InputSourcePtr;
} // end unnamed namespace
@@ -37,6 +48,7 @@ using namespace master_lexer_internal;
struct MasterLexer::MasterLexerImpl {
MasterLexerImpl() : source_(NULL), token_(MasterToken::NOT_STARTED),
+ total_size_(0), popped_size_(0),
paren_count_(0), last_was_eol_(true),
has_previous_(false),
previous_paren_count_(0),
@@ -80,11 +92,28 @@ struct MasterLexer::MasterLexerImpl {
separators_.test(c & 0x7f));
}
+ void setTotalSize() {
+ assert(source_ != NULL);
+ if (total_size_ != SOURCE_SIZE_UNKNOWN) {
+ const size_t current_size = source_->getSize();
+ if (current_size != SOURCE_SIZE_UNKNOWN) {
+ total_size_ += current_size;
+ } else {
+ total_size_ = SOURCE_SIZE_UNKNOWN;
+ }
+ }
+ }
+
std::vector<InputSourcePtr> sources_;
InputSource* source_; // current source (NULL if sources_ is empty)
MasterToken token_; // currently recognized token (set by a state)
std::vector<char> data_; // placeholder for string data
+ // Keep track of the total size of all sources and characters that have
+ // been read from sources already popped.
+ size_t total_size_; // accumulated size (# of chars) of sources
+ size_t popped_size_; // total size of sources that have been popped
+
// These are used in states, and defined here only as a placeholder.
// The main lexer class does not need these members.
size_t paren_count_; // nest count of the parentheses
@@ -128,15 +157,23 @@ MasterLexer::pushSource(const char* filename, std::string* error) {
impl_->source_ = impl_->sources_.back().get();
impl_->has_previous_ = false;
impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
return (true);
}
void
MasterLexer::pushSource(std::istream& input) {
- impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+ try {
+ impl_->sources_.push_back(InputSourcePtr(new InputSource(input)));
+ } catch (const InputSource::OpenError& ex) {
+ // Convert the "internal" exception to public one.
+ isc_throw(Unexpected, "Failed to push a stream to lexer: " <<
+ ex.what());
+ }
impl_->source_ = impl_->sources_.back().get();
impl_->has_previous_ = false;
impl_->last_was_eol_ = true;
+ impl_->setTotalSize();
}
void
@@ -145,6 +182,7 @@ MasterLexer::popSource() {
isc_throw(InvalidOperation,
"MasterLexer::popSource on an empty source");
}
+ impl_->popped_size_ += impl_->source_->getPosition();
impl_->sources_.pop_back();
impl_->source_ = impl_->sources_.empty() ? NULL :
impl_->sources_.back().get();
@@ -172,6 +210,20 @@ MasterLexer::getSourceLine() const {
return (impl_->sources_.back()->getCurrentLine());
}
+size_t
+MasterLexer::getTotalSourceSize() const {
+ return (impl_->total_size_);
+}
+
+size_t
+MasterLexer::getPosition() const {
+ size_t position = impl_->popped_size_;
+ BOOST_FOREACH(InputSourcePtr& src, impl_->sources_) {
+ position += src->getPosition();
+ }
+ return (position);
+}
+
const MasterToken&
MasterLexer::getNextToken(Options options) {
if (impl_->source_ == NULL) {
diff --git a/src/lib/dns/master_lexer.h b/src/lib/dns/master_lexer.h
index 1aa4255..31c6443 100644
--- a/src/lib/dns/master_lexer.h
+++ b/src/lib/dns/master_lexer.h
@@ -331,6 +331,16 @@ public:
const MasterToken token_;
};
+ /// \brief Special value for input source size meaning "unknown".
+ ///
+ /// This constant value will be used as a return value of
+ /// \c getTotalSourceSize() when the size of one of the pushed sources
+ /// is unknown. Note that this value itself is a valid integer in the
+ /// range of the type, so there's still a small possibility of
+ /// ambiguity. In practice, however, the value should be sufficiently
+ /// large that should eliminate the possibility.
+ static const size_t SOURCE_SIZE_UNKNOWN;
+
/// \brief Options for getNextToken.
///
/// A compound option, indicating multiple options are set, can be
@@ -387,6 +397,26 @@ public:
/// The caller can explicitly tell \c MasterLexer to stop using the
/// stream by calling the \c popSource() method.
///
+ /// The data in \c input must be complete at the time of this call.
+ /// The behavior of the lexer is undefined if the caller builds or adds
+ /// data in \c input after pushing it.
+ ///
+ /// Except for rare case system errors such as memory allocation failure,
+ /// this method is generally expected to be exception free. However,
+ /// it can still throw if it encounters an unexpected failure when it
+ /// tries to identify the "size" of the input source (see
+ /// \c getTotalSourceSize()). It's an unexpected result unless the
+ /// caller intentionally passes a broken stream; otherwise it would mean
+ /// some system-dependent unexpected behavior or possibly an internal bug.
+ /// In these cases it throws an \c Unexpected exception. Note that
+ /// this version of the method doesn't return a boolean unlike the
+ /// other version that takes a file name; since this failure is really
+ /// unexpected and can be critical, it doesn't make sense to give the
+ /// caller an option to continue (other than by explicitly catching the
+ /// exception).
+ ///
+ /// \throw Unexpected An unexpected failure happens in initialization.
+ ///
/// \param input An input stream object that produces textual
/// representation of DNS RRs.
void pushSource(std::istream& input);
@@ -443,6 +473,73 @@ public:
/// \return The current line number of the source (see the description)
size_t getSourceLine() const;
+ /// \brief Return the total size of pushed sources.
+ ///
+ /// This method returns the sum of the size of sources that have been
+ /// pushed to the lexer by the time of the call. It would give the
+ /// caller some hint about the amount of data the lexer is working on.
+ ///
+ /// The size of a normal file is equal to the file size at the time of
+ /// the source is pushed. The size of other type of input stream is
+ /// the size of the data available in the stream at the time of the
+ /// source is pushed.
+ ///
+ /// In some special cases, it's possible that the size of the file or
+ /// stream is unknown. It happens, for example, if the standard input
+ /// is associated with a pipe from the output of another process and it's
+ /// specified as an input source. If the size of some of the pushed
+ /// source is unknown, this method returns SOURCE_SIZE_UNKNOWN.
+ ///
+ /// The total size won't change when a source is popped. So the return
+ /// values of this method will monotonically increase or
+ /// \c SOURCE_SIZE_UNKNOWN; once it returns \c SOURCE_SIZE_UNKNOWN,
+ /// any subsequent call will also result in that value, by the above
+ /// definition.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// \throw None
+ size_t getTotalSourceSize() const;
+
+ /// \brief Return the position of lexer in the pushed sources so far.
+ ///
+ /// This method returns the position in terms of the number of recognized
+ /// characters from all sources that have been pushed by the time of the
+ /// call. Conceptually, the position in a single source is the offset
+ /// from the beginning of the file or stream to the current "read cursor"
+ /// of the lexer. The return value of this method is the sum of the
+ /// positions in all the pushed sources. If any of the sources has
+ /// already been popped, the position of the source at the time of the
+ /// pop operation will be used for the calculation.
+ ///
+ /// If the lexer reaches the end for each of all the pushed sources,
+ /// the return value should be equal to that of \c getTotalSourceSize().
+ /// It's generally expected that a source is popped when the lexer
+ /// reaches the end of the source. So, when the application of this
+ /// class parses all contents of all sources, possibly with multiple
+ /// pushes and pops, the return value of this method and
+ /// \c getTotalSourceSize() should be identical (unless the latter
+ /// returns SOURCE_SIZE_UNKNOWN). But this is not necessarily
+ /// guaranteed as the application can pop a source in the middle of
+ /// parsing it.
+ ///
+ /// Before pushing any source, it returns 0.
+ ///
+ /// The return values of this method and \c getTotalSourceSize() would
+ /// give the caller an idea of the progress of the lexer at the time of
+ /// the call. Note, however, that since it's not predictable whether
+ /// more sources will be pushed after the call, the progress determined
+ /// this way may not make much sense; it can only give an informational
+ /// hint of the progress.
+ ///
+ /// Note that the conceptual "read cursor" would move backward after a
+ /// call to \c ungetToken(), in which case this method will return a
+ /// smaller value. That is, unlike \c getTotalSourceSize(), return
+ /// values of this method may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
/// \brief Parse and return another token from the input.
///
/// It reads a bit of the last opened source and produces another token
diff --git a/src/lib/dns/master_lexer_inputsource.cc b/src/lib/dns/master_lexer_inputsource.cc
index 03801f6..cc1f505 100644
--- a/src/lib/dns/master_lexer_inputsource.cc
+++ b/src/lib/dns/master_lexer_inputsource.cc
@@ -15,6 +15,9 @@
#include <dns/master_lexer_inputsource.h>
#include <dns/master_lexer.h>
+#include <istream>
+#include <iostream>
+#include <cassert>
#include <cerrno>
#include <cstring>
@@ -31,6 +34,54 @@ createStreamName(const std::istream& input_stream) {
return (ss.str());
}
+size_t
+getStreamSize(std::istream& is) {
+ errno = 0; // see below
+ is.seekg(0, std::ios_base::end);
+ if (is.bad()) {
+ // This means the istream has an integrity error. It doesn't make
+ // sense to continue from this point, so we treat it as a fatal error.
+ isc_throw(InputSource::OpenError,
+ "failed to seek end of input source");
+ } else if (is.fail() || errno != 0) {
+ // This is an error specific to seekg(). There can be several
+ // reasons, but the most likely cause in this context is that the
+ // stream is associated with a special type of file such as a pipe.
+ // In this case, it's more likely that other main operations of
+ // the input source work fine, so we continue with just setting
+ // the stream size to "unknown".
+ //
+ // (At least some versions of) Solaris + SunStudio shows deviant
+ // behavior here: seekg() apparently calls lseek(2) internally, but
+ // even if it fails it doesn't set the error bits of istream. That will
+ // confuse the rest of this function, so, as a heuristic workaround
+ // we check errno and handle any non 0 value as fail().
+ is.clear(); // clear this error not to confuse later ops.
+ return (MasterLexer::SOURCE_SIZE_UNKNOWN);
+ }
+ const std::streampos len = is.tellg();
+ size_t ret = len;
+ if (len == static_cast<std::streampos>(-1)) { // cast for some compilers
+ if (!is.fail()) {
+ // tellg() returns -1 if istream::fail() would be true, but it's
+ // not guaranteed that it shouldn't be returned in other cases.
+ // In fact, with the combination of SunStudio and stlport,
+ // a stringstream created by the default constructor showed that
+ // behavior. We treat such cases as an unknown size.
+ ret = MasterLexer::SOURCE_SIZE_UNKNOWN;
+ } else {
+ isc_throw(InputSource::OpenError, "failed to get input size");
+ }
+ }
+ is.seekg(0, std::ios::beg);
+ if (is.fail()) {
+ isc_throw(InputSource::OpenError,
+ "failed to seek beginning of input source");
+ }
+ assert(len >= 0 || ret == MasterLexer::SOURCE_SIZE_UNKNOWN);
+ return (ret);
+}
+
} // end of unnamed namespace
// Explicit definition of class static constant. The value is given in the
@@ -42,31 +93,44 @@ InputSource::InputSource(std::istream& input_stream) :
line_(1),
saved_line_(line_),
buffer_pos_(0),
+ total_pos_(0),
name_(createStreamName(input_stream)),
- input_(input_stream)
+ input_(input_stream),
+ input_size_(getStreamSize(input_))
{}
-InputSource::InputSource(const char* filename) :
- at_eof_(false),
- line_(1),
- saved_line_(line_),
- buffer_pos_(0),
- name_(filename),
- input_(file_stream_)
-{
+namespace {
+// A helper to initialize InputSource::input_ in the member initialization
+// list.
+std::istream&
+openFileStream(std::ifstream& file_stream, const char* filename) {
errno = 0;
- file_stream_.open(filename);
- if (file_stream_.fail()) {
+ file_stream.open(filename);
+ if (file_stream.fail()) {
std::string error_txt("Error opening the input source file: ");
error_txt += filename;
if (errno != 0) {
error_txt += "; possible cause: ";
error_txt += std::strerror(errno);
}
- isc_throw(OpenError, error_txt);
+ isc_throw(InputSource::OpenError, error_txt);
}
+
+ return (file_stream);
+}
}
+InputSource::InputSource(const char* filename) :
+ at_eof_(false),
+ line_(1),
+ saved_line_(line_),
+ buffer_pos_(0),
+ total_pos_(0),
+ name_(filename),
+ input_(openFileStream(file_stream_, filename)),
+ input_size_(getStreamSize(input_))
+{}
+
InputSource::~InputSource()
{
if (file_stream_.is_open()) {
@@ -103,6 +167,7 @@ InputSource::getChar() {
const int c = buffer_[buffer_pos_];
++buffer_pos_;
+ ++total_pos_;
if (c == '\n') {
++line_;
}
@@ -119,6 +184,7 @@ InputSource::ungetChar() {
"Cannot skip before the start of buffer");
} else {
--buffer_pos_;
+ --total_pos_;
if (buffer_[buffer_pos_] == '\n') {
--line_;
}
@@ -127,6 +193,8 @@ InputSource::ungetChar() {
void
InputSource::ungetAll() {
+ assert(total_pos_ >= buffer_pos_);
+ total_pos_ -= buffer_pos_;
buffer_pos_ = 0;
line_ = saved_line_;
at_eof_ = false;
diff --git a/src/lib/dns/master_lexer_inputsource.h b/src/lib/dns/master_lexer_inputsource.h
index 1a4497f..2ad0100 100644
--- a/src/lib/dns/master_lexer_inputsource.h
+++ b/src/lib/dns/master_lexer_inputsource.h
@@ -65,12 +65,16 @@ public:
/// \brief Constructor which takes an input stream. The stream is
/// read-from, but it is not closed.
+ ///
+ /// \throws OpenError If the data size of the input stream cannot be
+ /// detected.
explicit InputSource(std::istream& input_stream);
/// \brief Constructor which takes a filename to read from. The
/// associated file stream is managed internally.
///
- /// \throws OpenError when opening the input file fails.
+ /// \throws OpenError when opening the input file fails or the size of
+ /// the file cannot be detected.
explicit InputSource(const char* filename);
/// \brief Destructor
@@ -83,6 +87,34 @@ public:
return (name_);
}
+ /// \brief Returns the size of the input source in bytes.
+ ///
+ /// If the size is unknown, it returns \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// See \c MasterLexer::getTotalSourceSize() for the definition of
+ /// the size of sources and for when the size can be unknown.
+ ///
+ /// \throw None
+ size_t getSize() const { return (input_size_); }
+
+ /// \brief Returns the current read position in the input source.
+ ///
+ /// This method returns the position of the character that was last
+ /// retrieved from the source. Unless some characters have been
+ /// "ungotten" by \c ungetChar() or \c ungetAll(), this value is equal
+ /// to the number of calls to \c getChar() until it reaches the
+ /// END_OF_STREAM. Note that the position of the first character in
+ /// the source is 1. At the point of the last character, the return value
+ /// of this method should be equal to that of \c getSize(), and
+ /// recognizing END_OF_STREAM doesn't increase the position.
+ ///
+ /// If \c ungetChar() or \c ungetAll() is called, the position is
+ /// decreased by the number of "ungotten" characters. So the return
+ /// values may not always monotonically increase.
+ ///
+ /// \throw None
+ size_t getPosition() const { return (total_pos_); }
+
/// \brief Returns if the input source is at end of file.
bool atEOF() const {
return (at_eof_);
@@ -142,10 +174,12 @@ private:
std::vector<char> buffer_;
size_t buffer_pos_;
+ size_t total_pos_;
const std::string name_;
std::ifstream file_stream_;
std::istream& input_;
+ const size_t input_size_;
};
} // namespace master_lexer_internal
diff --git a/src/lib/dns/master_loader.cc b/src/lib/dns/master_loader.cc
index 7ad6c0f..29e7e1d 100644
--- a/src/lib/dns/master_loader.cc
+++ b/src/lib/dns/master_loader.cc
@@ -76,7 +76,8 @@ public:
previous_name_(false),
complete_(false),
seen_error_(false),
- warn_rfc1035_ttl_(true)
+ warn_rfc1035_ttl_(true),
+ rr_count_(0)
{}
void pushSource(const std::string& filename, const Name& current_origin) {
@@ -103,6 +104,9 @@ public:
bool loadIncremental(size_t count_limit);
+ size_t getSize() const { return (lexer_.getTotalSourceSize()); }
+ size_t getPosition() const { return (lexer_.getPosition()); }
+
private:
void reportError(const std::string& filename, size_t line,
const std::string& reason)
@@ -219,28 +223,28 @@ private:
// after the RR class below.
}
- boost::scoped_ptr<RRClass> rrclass;
- try {
- rrclass.reset(new RRClass(rrparam_token.getString()));
+ const MaybeRRClass rrclass =
+ RRClass::createFromText(rrparam_token.getString());
+ if (rrclass) {
+ // FIXME: The following code re-parses the rrparam_token to
+ // make an RRClass instead of using the MaybeRRClass above,
+ // because some old versions of boost::optional (that we
+ // still want to support) have a bug (see trac #2593). This
+ // workaround should be removed at some point in the future.
+ if (RRClass(rrparam_token.getString()) != zone_class_) {
+ isc_throw(InternalException, "Class mismatch: " << *rrclass <<
+ " vs. " << zone_class_);
+ }
rrparam_token = lexer_.getNextToken(MasterToken::STRING);
- } catch (const InvalidRRClass&) {
- // If it's not an rrclass here, use the zone's class.
- rrclass.reset(new RRClass(zone_class_));
}
// If we couldn't parse TTL earlier in the stream (above), try
// again at current location.
- if (!explicit_ttl &&
- setCurrentTTL(rrparam_token.getString())) {
+ if (!explicit_ttl && setCurrentTTL(rrparam_token.getString())) {
explicit_ttl = true;
rrparam_token = lexer_.getNextToken(MasterToken::STRING);
}
- if (*rrclass != zone_class_) {
- isc_throw(InternalException, "Class mismatch: " << *rrclass <<
- "vs. " << zone_class_);
- }
-
// Return the current string token's value as the RRType.
return (RRType(rrparam_token.getString()));
}
@@ -398,7 +402,7 @@ private:
shared_ptr<Name> last_name_; // Last seen name (for INITAL_WS handling)
const RRClass zone_class_;
MasterLoaderCallbacks callbacks_;
- AddRRCallback add_callback_;
+ const AddRRCallback add_callback_;
boost::scoped_ptr<RRTTL> default_ttl_; // Default TTL of RRs used when
// unspecified. If NULL no default
// is known.
@@ -418,12 +422,14 @@ private:
vector<IncludeInfo> include_info_;
bool previous_name_; // True if there was a previous name in this file
// (false at the beginning or after an $INCLUDE line)
+
public:
bool complete_; // All work done.
bool seen_error_; // Was there at least one error during the
// load?
bool warn_rfc1035_ttl_; // should warn if implicit TTL determination
// from the previous RR is used.
+ size_t rr_count_; // number of RRs successfully loaded
};
// A helper method of loadIncremental, parsing the first token of a new line.
@@ -554,6 +560,7 @@ MasterLoader::MasterLoaderImpl::loadIncremental(size_t count_limit) {
rdata);
// Good, we loaded another one
++count;
+ ++rr_count_;
} else {
seen_error_ = true;
if (!many_errors_) {
@@ -630,5 +637,15 @@ MasterLoader::loadedSucessfully() const {
return (impl_->complete_ && !impl_->seen_error_);
}
+size_t
+MasterLoader::getSize() const {
+ return (impl_->getSize());
+}
+
+size_t
+MasterLoader::getPosition() const {
+ return (impl_->getPosition());
+}
+
} // end namespace dns
} // end namespace isc
diff --git a/src/lib/dns/master_loader.h b/src/lib/dns/master_loader.h
index 9d8a755..37b2ef4 100644
--- a/src/lib/dns/master_loader.h
+++ b/src/lib/dns/master_loader.h
@@ -142,6 +142,44 @@ public:
/// finishing the load.
bool loadedSucessfully() const;
+ /// \brief Return the total size of the zone files and streams.
+ ///
+ /// This method returns the size of the source of the zone to be loaded
+ /// (master zone files or streams) that is known at the time of the call.
+ /// For a zone file, it's the size of the file; for a stream, it's the
+ /// size of the data (in bytes) available at the start of the load.
+ /// If separate zone files are included via the $INCLUDE directive, the
+ /// sum of the sizes of these files are added.
+ ///
+ /// If the loader is constructed with a stream, the size can be
+ /// "unknown" as described for \c MasterLexer::getTotalSourceSize().
+ /// In this case this method always returns
+ /// \c MasterLexer::SOURCE_SIZE_UNKNOWN.
+ ///
+ /// If the loader is constructed with a zone file, this method
+ /// initially returns 0. So until either \c load() or \c loadIncremental()
+ /// is called, the value is meaningless.
+ ///
+ /// Note that when the source includes separate files, this method
+ /// cannot take into account the included files that the loader has not
+ /// recognized at the time of call. So it's possible that this method
+ /// returns different values at different times of call.
+ ///
+ /// \throw None
+ size_t getSize() const;
+
+ /// \brief Return the position of the loader in zone.
+ ///
+ /// This method returns a conceptual "position" of the loader in the
+ /// zone to be loaded. Specifically, it returns the total number of
+ /// characters contained in the zone files and streams and recognized
+ /// by the loader. Before starting the load it returns 0; on successful
+ /// completion it will be equal to the return value of \c getSize()
+ /// (unless the latter returns \c MasterLexer::SOURCE_SIZE_UNKNOWN).
+ ///
+ /// \throw None
+ size_t getPosition() const;
+
private:
class MasterLoaderImpl;
MasterLoaderImpl* impl_;
@@ -151,3 +189,7 @@ private:
} // end namespace isc
#endif // MASTER_LOADER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/master_loader_callbacks.h b/src/lib/dns/master_loader_callbacks.h
index f9cc18b..e725595 100644
--- a/src/lib/dns/master_loader_callbacks.h
+++ b/src/lib/dns/master_loader_callbacks.h
@@ -100,7 +100,7 @@ public:
/// If the caller of the loader wants to abort, it is possible to throw
/// from the callback, which aborts the load.
void error(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
error_(source_name, source_line, reason);
}
@@ -117,7 +117,7 @@ public:
/// may be false positives), it is possible to throw from inside the
/// callback.
void warning(const std::string& source_name, size_t source_line,
- const std::string& reason)
+ const std::string& reason) const
{
warning_(source_name, source_line, reason);
}
@@ -133,7 +133,7 @@ public:
static MasterLoaderCallbacks getNullCallbacks();
private:
- IssueCallback error_, warning_;
+ const IssueCallback error_, warning_;
};
}
diff --git a/src/lib/dns/python/Makefile.am b/src/lib/dns/python/Makefile.am
index 6dd94b6..a221bfe 100644
--- a/src/lib/dns/python/Makefile.am
+++ b/src/lib/dns/python/Makefile.am
@@ -25,6 +25,9 @@ libb10_pydnspp_la_SOURCES += tsigrecord_python.cc tsigrecord_python.h
libb10_pydnspp_la_SOURCES += tsig_python.cc tsig_python.h
libb10_pydnspp_la_SOURCES += edns_python.cc edns_python.h
libb10_pydnspp_la_SOURCES += message_python.cc message_python.h
+libb10_pydnspp_la_SOURCES += rrset_collection_python.cc
+libb10_pydnspp_la_SOURCES += rrset_collection_python.h
+libb10_pydnspp_la_SOURCES += zone_checker_python.cc zone_checker_python.h
libb10_pydnspp_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
libb10_pydnspp_la_CXXFLAGS = $(AM_CXXFLAGS) $(PYTHON_CXXFLAGS)
@@ -43,6 +46,8 @@ pydnspp_la_LDFLAGS = $(PYTHON_LDFLAGS)
EXTRA_DIST = tsigerror_python_inc.cc
EXTRA_DIST += message_python_inc.cc
EXTRA_DIST += nsec3hash_python_inc.cc
+EXTRA_DIST += rrset_collection_python_inc.cc
+EXTRA_DIST += zone_checker_python_inc.cc
# Python prefers .so, while some OSes (specifically MacOS) use a different
# suffix for dynamic objects. -module is necessary to work this around.
diff --git a/src/lib/dns/python/pydnspp.cc b/src/lib/dns/python/pydnspp.cc
index 6d1bd89..c75c737 100644
--- a/src/lib/dns/python/pydnspp.cc
+++ b/src/lib/dns/python/pydnspp.cc
@@ -50,12 +50,16 @@
#include "rrset_python.h"
#include "rrttl_python.h"
#include "rrtype_python.h"
+#include "rrset_collection_python.h"
#include "serial_python.h"
#include "tsigerror_python.h"
#include "tsigkey_python.h"
#include "tsig_python.h"
#include "tsig_rdata_python.h"
#include "tsigrecord_python.h"
+#include "zone_checker_python.h"
+
+#include "zone_checker_python_inc.cc"
using namespace isc::dns;
using namespace isc::dns::python;
@@ -728,6 +732,11 @@ initModulePart_TSIGRecord(PyObject* mod) {
return (true);
}
+PyMethodDef methods[] = {
+ { "check_zone", internal::pyCheckZone, METH_VARARGS, dns_checkZone_doc },
+ { NULL, NULL, 0, NULL }
+};
+
PyModuleDef pydnspp = {
{ PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
"pydnspp",
@@ -737,13 +746,13 @@ PyModuleDef pydnspp = {
"and OutputBuffer for instance), and others may be necessary, but "
"were not up to now.",
-1,
- NULL,
+ methods,
NULL,
NULL,
NULL,
NULL
};
-}
+} // unnamed namespace
PyMODINIT_FUNC
PyInit_pydnspp(void) {
@@ -864,5 +873,13 @@ PyInit_pydnspp(void) {
return (NULL);
}
+ if (!initModulePart_RRsetCollectionBase(mod)) {
+ return (NULL);
+ }
+
+ if (!initModulePart_RRsetCollection(mod)) {
+ return (NULL);
+ }
+
return (mod);
}
diff --git a/src/lib/dns/python/rrset_collection_python.cc b/src/lib/dns/python/rrset_collection_python.cc
new file mode 100644
index 0000000..cfdcdae
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.cc
@@ -0,0 +1,453 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+//#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/name_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrset_collection.h>
+
+#include <string>
+#include <sstream>
+#include <stdexcept>
+
+using namespace std;
+using namespace isc::util::python;
+using namespace isc::dns;
+using namespace isc::dns::python;
+
+// Import pydoc text
+#include "rrset_collection_python_inc.cc"
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+// RRsetCollectionBase: the base RRsetCollection class in Python.
+//
+// Any derived RRsetCollection class is supposed to be inherited from this
+// class:
+// - If the derived class is implemented via a C++ wrapper (associated with
+// a C++ implementation of RRsetCollection), its PyTypeObject should
+// specify rrset_collection_base_type for tp_base. Its C/C++-representation
+// of objects should be compatible with s_RRsetCollection, and the wrapper
+// should set its cppobj member to point to the corresponding C++
+// RRsetCollection object. Normally it doesn't have to provide Python
+// wrapper of find(); the Python interpreter will then call find() on
+// the base class, which ensures that the corresponding C++ version of
+// find() will be used.
+// - If the derived class is implemented purely in Python, it must implement
+// find() in Python within the class. As explained in the first bullet,
+// the base class method is generally expected to be used only for C++
+// wrapper of RRsetCollection derived class.
+namespace {
+int
+RRsetCollectionBase_init(PyObject*, PyObject*, PyObject*) {
+ PyErr_SetString(PyExc_TypeError,
+ "RRsetCollectionBase cannot be instantiated directly");
+ return (-1);
+}
+
+void
+RRsetCollectionBase_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+ // Any C++-wrapper of derived RRsetCollection class should have its own
+ // destroy function (as it may manage cppobj in its own way);
+ // Python-only derived classes shouldn't set cppobj (which is
+ // 0-initialized). So this assertion must hold.
+ assert(self->cppobj == NULL);
+
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollectionBase_find(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+
+ // If this function is called with cppobj being NULL, this means
+ // a pure-Python derived class skips implementing its own find().
+ // This is an error (see general description above).
+ if (self->cppobj == NULL) {
+ PyErr_Format(PyExc_TypeError, "find() is not implemented in the "
+ "derived RRsetCollection class");
+ return (NULL);
+ }
+
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ ConstRRsetPtr found_rrset = self->cppobj->find(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (found_rrset) {
+ return (createRRsetObject(*found_rrset));
+ }
+ Py_RETURN_NONE;
+ }
+ } catch (const RRsetCollectionError& ex) {
+ PyErr_SetString(po_RRsetCollectionError, ex.what());
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollectionBase.find: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollectionBase_methods[] = {
+ { "find", RRsetCollectionBase_find, METH_VARARGS,
+ RRsetCollectionBase_find_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+namespace isc {
+namespace dns {
+namespace python {
+// Definition of class specific exception(s)
+PyObject* po_RRsetCollectionError;
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_base_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollectionBase",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollectionBase_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, // tp_flags (it's inheritable)
+ RRsetCollectionBase_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollectionBase_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollectionBase_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollectionBase(PyObject* mod) {
+ if (!initClass(rrset_collection_base_type, "RRsetCollectionBase", mod)) {
+ return (false);
+ }
+
+ try {
+ po_RRsetCollectionError =
+ PyErr_NewException("dns.RRsetCollectionError",
+ po_IscException, NULL);
+ PyObjectContainer(po_RRsetCollectionError).installToModule(
+ mod, "RRsetCollectionError");
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in RRsetCollectionBase initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (false);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected failure in "
+ "RRsetCollectionBase initialization");
+ return (false);
+ }
+
+ return (true);
+}
+
+//
+// RRsetCollection
+//
+
+namespace {
+// Shortcut type which would be convenient for adding class variables safely.
+typedef CPPPyObjectContainer<s_RRsetCollection, RRsetCollection> RRsetCollectionContainer;
+
+int
+RRsetCollection_init(PyObject* po_self, PyObject* args, PyObject*) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ const char* filename;
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ Py_buffer py_buf;
+
+ if (PyArg_ParseTuple(args, "sO!O!", &filename, &name_type, &po_name,
+ &rrclass_type, &po_rrclass)) {
+ self->cppobj =
+ new RRsetCollection(filename, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "y*O!O!", &py_buf, &name_type,
+ &po_name,&rrclass_type, &po_rrclass)) {
+ PyErr_Clear(); // clear the error for the first ParseTuple
+ const char* const cp = static_cast<const char*>(py_buf.buf);
+ std::istringstream iss(string(cp, cp + py_buf.len));
+ self->cppobj =
+ new RRsetCollection(iss, PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass));
+ return (0);
+ } else if (PyArg_ParseTuple(args, "")) {
+ PyErr_Clear(); // clear the error for the second ParseTuple
+ self->cppobj = new RRsetCollection;
+ return (0);
+ }
+ } catch (const exception& ex) {
+ const string ex_what = "Failed to construct RRsetCollection object: " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (-1);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (-1);
+ }
+
+ // Default error string isn't helpful when it takes multiple combinations
+ // of args. We provide our own.
+ PyErr_SetString(PyExc_TypeError, "Invalid argument(s) to RRsetCollection "
+ "constructor; see pydoc");
+
+ return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ delete self->cppobj;
+ self->cppobj = NULL;
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyObject*
+RRsetCollection_addRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_rrset;
+ if (PyArg_ParseTuple(args, "O", &po_rrset)) {
+ if (!PyRRset_Check(po_rrset)) {
+ return (setTypeError(po_rrset, "rrset", "RRset"));
+ }
+ static_cast<RRsetCollection*>(self->cppobj)->addRRset(
+ PyRRset_ToRRsetPtr(po_rrset));
+ Py_RETURN_NONE;
+ }
+ } catch (const InvalidParameter& ex) { // duplicate add
+ PyErr_SetString(PyExc_ValueError, ex.what());
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in "
+ "RRsetCollection.add_rrset: " + string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+PyObject*
+RRsetCollection_removeRRset(PyObject* po_self, PyObject* args) {
+ s_RRsetCollection* self = static_cast<s_RRsetCollection*>(po_self);
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrtype;
+
+ if (PyArg_ParseTuple(args, "OOO", &po_name, &po_rrclass, &po_rrtype)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "rrclass", "RRClass"));
+ }
+ if (!PyRRType_Check(po_rrtype)) {
+ return (setTypeError(po_rrtype, "rrtype", "RRType"));
+ }
+ const bool result =
+ static_cast<RRsetCollection*>(self->cppobj)->removeRRset(
+ PyName_ToName(po_name), PyRRClass_ToRRClass(po_rrclass),
+ PyRRType_ToRRType(po_rrtype));
+ if (result) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (...) {}
+
+ return (NULL);
+}
+
+// This list contains the actual set of functions we have in
+// python. Each entry has
+// 1. Python method name
+// 2. Our static function here
+// 3. Argument type
+// 4. Documentation
+PyMethodDef RRsetCollection_methods[] = {
+ { "add_rrset", RRsetCollection_addRRset, METH_VARARGS,
+ RRsetCollection_addRRset_doc },
+ { "remove_rrset", RRsetCollection_removeRRset, METH_VARARGS,
+ RRsetCollection_removeRRset_doc },
+ { NULL, NULL, 0, NULL }
+};
+} // end of unnamed namespace
+
+// This defines the complete type for reflection in python and
+// parsing of PyObject* to s_RRsetCollection
+// Most of the functions are not actually implemented and NULL here.
+PyTypeObject rrset_collection_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "dns.RRsetCollection",
+ sizeof(s_RRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollection_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ RRsetCollection_doc,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ RRsetCollection_methods, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ &rrset_collection_base_type, // tp_base
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollection_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+
+// Module Initialization, all statics are initialized here
+bool
+initModulePart_RRsetCollection(PyObject* mod) {
+ if (!initClass(rrset_collection_type, "RRsetCollection", mod)) {
+ return (false);
+ }
+
+ return (true);
+}
+
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/rrset_collection_python.h b/src/lib/dns/python/rrset_collection_python.h
new file mode 100644
index 0000000..ea442bb
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python.h
@@ -0,0 +1,58 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_RRSETCOLLECTION_H
+#define PYTHON_RRSETCOLLECTION_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+class RRsetCollectionBase;
+
+namespace python {
+
+// The s_* Class simply covers one instantiation of the object.
+// This structure will be commonly used for all derived classes of
+// RRsetCollectionBase. cppobj will point to an instance of the specific
+// derived class of (C++) RRsetCollectionBase.
+//
+// A C++ wrapper for Python version of RRsetCollection should set this
+// variable, and skip the implementation of C++ wrapper of find() method.
+class s_RRsetCollection : public PyObject {
+public:
+ s_RRsetCollection() : cppobj(NULL) {}
+ RRsetCollectionBase* cppobj;
+};
+
+// Python type information for RRsetCollectionBase
+extern PyTypeObject rrset_collection_base_type;
+
+// Python type information for dns.RRsetCollection
+extern PyTypeObject rrset_collection_type;
+
+// Class specific exceptions
+extern PyObject* po_RRsetCollectionError;
+
+bool initModulePart_RRsetCollectionBase(PyObject* mod);
+bool initModulePart_RRsetCollection(PyObject* mod);
+
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_RRSETCOLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/rrset_collection_python_inc.cc b/src/lib/dns/python/rrset_collection_python_inc.cc
new file mode 100644
index 0000000..f6eb8a3
--- /dev/null
+++ b/src/lib/dns/python/rrset_collection_python_inc.cc
@@ -0,0 +1,148 @@
+namespace {
+// Modifications
+// - libdns++ => isc.dns, libdatasrc => isc.datasrc
+// - note about the direct construction.
+// - add note about iteration
+const char* const RRsetCollectionBase_doc = "\
+Generic class to represent a set of RRsets.\n\
+\n\
+This is a generic container and the stored set of RRsets does not\n\
+necessarily form a valid zone (e.g. there doesn't necessarily have to\n\
+be an SOA at the \"origin\"). Instead, it will be used to represent a\n\
+single zone for the purpose of zone loading/checking. It provides a\n\
+simple find() method to find an RRset for the given name and type (and\n\
+maybe class) and a way to iterate over all RRsets.\n\
+\n\
+ Note: in the initial version, iteration is not yet supported.\n\
+\n\
+See RRsetCollection for a simple isc.dns implementation. Other modules\n\
+such as isc.datasrc will have another implementation.\n\
+\n\
+This base class cannot be directly instantiated. Such an attempt will\n\
+result in a TypeError exception.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+// - NULL => None
+// - added types of params
+const char* const RRsetCollectionBase_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, None is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name (isc.dns.Name) The name of the RRset to search for.\n\
+ rrtype (isc.dns.RRType) The type of the RRset to search for.\n\
+ rrclass (isc.dns.RRClass) The class of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, None otherwise.\n\
+";
+
+
+// Modifications
+// - libdns++ => isc.dns
+// - remove STL
+// - MasterLoaderError => IscException
+// - added types of params
+// - input_stream => input, stream => bytes
+const char* const RRsetCollection_doc = "\
+Derived class implementation of RRsetCollectionBase for isc.dns module.\n\
+\n\
+RRsetCollection()\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor creates an empty collection without any data in\n\
+ it. RRsets can be added to the collection with the add_rrset()\n\
+ method.\n\
+\n\
+RRsetCollection(filename, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ The origin and rrclass arguments are required for the zone\n\
+ loading, but RRsetCollection itself does not do any validation,\n\
+ and the collection of RRsets does not have to form a valid zone.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ filename (str) Name of a file containing a collection of RRs in the\n\
+ master file format (which may or may not form a valid\n\
+ zone).\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+RRsetCollection(input, origin, rrclass)\n\
+\n\
+ Constructor.\n\
+\n\
+ This constructor is similar to the previous one, but instead of\n\
+ taking a filename to load a zone from, it takes a bytes object,\n\
+ representing the zone contents in text.\n\
+ The constructor throws IscException if there is an error\n\
+ during loading.\n\
+\n\
+ Parameters:\n\
+ input (bytes) Textual representation of the zone.\n\
+ origin (isc.dns.Name) The zone origin.\n\
+ rrclass (isc.dns.RRClass) The zone class.\n\
+\n\
+";
+
+// Modifications
+// - void => None
+// - InvalidParameter => ValueError
+// - remove ownership related points (doesn't apply here)
+const char* const RRsetCollection_addRRset_doc = "\
+add_rrset(rrset) -> None\n\
+\n\
+Add an RRset to the collection.\n\
+\n\
+Does not do any validation whether rrset belongs to a particular zone\n\
+or not.\n\
+\n\
+It throws a ValueError exception if an rrset with the same\n\
+class, type and name already exists.\n\
+\n\
+";
+
+// Modifications
+// - ConstRRset => RRset
+const char* const RRsetCollection_find_doc = "\
+find(name, rrclass, rrtype) -> isc.dns.RRset\n\
+\n\
+Find a matching RRset in the collection.\n\
+\n\
+Returns the RRset in the collection that exactly matches the given\n\
+name, rrclass and rrtype. If no matching RRset is found, NULL is\n\
+returned.\n\
+\n\
+Parameters:\n\
+ name The name of the RRset to search for.\n\
+ rrclass The class of the RRset to search for.\n\
+ rrtype The type of the RRset to search for.\n\
+\n\
+Return Value(s): The RRset if found, NULL otherwise.\n\
+";
+
+// Modifications
+// // - true/false => True/False
+const char* const RRsetCollection_removeRRset_doc = "\
+remove_rrset(name, rrclass, rrtype) -> bool\n\
+\n\
+Remove an RRset from the collection.\n\
+\n\
+RRset(s) matching the name, rrclass and rrtype are removed from the\n\
+collection.\n\
+\n\
+True if a matching RRset was deleted, False if no such RRset exists.\n\
+\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/python/tests/Makefile.am b/src/lib/dns/python/tests/Makefile.am
index 4b0ea9f..de6b010 100644
--- a/src/lib/dns/python/tests/Makefile.am
+++ b/src/lib/dns/python/tests/Makefile.am
@@ -12,12 +12,14 @@ PYTESTS += rrclass_python_test.py
PYTESTS += rrset_python_test.py
PYTESTS += rrttl_python_test.py
PYTESTS += rrtype_python_test.py
+PYTESTS += rrset_collection_python_test.py
PYTESTS += serial_python_test.py
PYTESTS += tsig_python_test.py
PYTESTS += tsig_rdata_python_test.py
PYTESTS += tsigerror_python_test.py
PYTESTS += tsigkey_python_test.py
PYTESTS += tsigrecord_python_test.py
+PYTESTS += zone_checker_python_test.py
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += testutil.py
diff --git a/src/lib/dns/python/tests/rrset_collection_python_test.py b/src/lib/dns/python/tests/rrset_collection_python_test.py
new file mode 100644
index 0000000..2cf286e
--- /dev/null
+++ b/src/lib/dns/python/tests/rrset_collection_python_test.py
@@ -0,0 +1,140 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import os
+import unittest
+from pydnspp import *
+
+# This should refer to the testdata diretory for the C++ tests.
+TESTDATA_DIR = os.environ["TESTDATA_PATH"].split(':')[0]
+
+class RRsetCollectionBaseTest(unittest.TestCase):
+ def test_init(self):
+ # direct instantiation of the base class is prohibited.
+ self.assertRaises(TypeError, RRsetCollectionBase)
+
+class RRsetCollectionTest(unittest.TestCase):
+ def test_init_fail(self):
+ # check various failure cases on construction (other normal cases are
+ # covered as part of other tests)
+
+ # bad args
+ self.assertRaises(TypeError, RRsetCollection, 1)
+ self.assertRaises(TypeError, RRsetCollection, # extra arg
+ b'example. 0 A 192.0.2.1',
+ Name('example'), RRClass.IN(), 1)
+ self.assertRaises(TypeError, RRsetCollection, # incorrect order
+ b'example. 0 A 192.0.2.1', RRClass.IN(),
+ Name('example'))
+
+ # constructor will result in C++ exception.
+ self.assertRaises(IscException, RRsetCollection,
+ TESTDATA_DIR + '/no_such_file', Name('example.org'),
+ RRClass.IN())
+
+ def check_find_result(self, rrsets):
+ # Commonly used check pattern
+ found = rrsets.find(Name('www.example.org'), RRClass.IN(), RRType.A())
+ self.assertNotEqual(None, found)
+ self.assertEqual(Name('www.example.org'), found.get_name())
+ self.assertEqual(RRClass.IN(), found.get_class())
+ self.assertEqual(RRType.A(), found.get_type())
+ self.assertEqual('192.0.2.1', found.get_rdata()[0].to_text())
+
+ def test_find(self):
+ # Checking the underlying find() is called as intended, both for
+ # success and failure cases, and with two different constructors.
+ rrsets = RRsetCollection(TESTDATA_DIR + '/example.org',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ rrsets = RRsetCollection(b'www.example.org. 3600 IN A 192.0.2.1',
+ Name('example.org'), RRClass.IN())
+ self.check_find_result(rrsets)
+ self.assertEqual(None, rrsets.find(Name('example.org'), RRClass.IN(),
+ RRType.A()))
+
+ def test_find_badargs(self):
+ rrsets = RRsetCollection()
+
+ # Check bad arguments: bad types
+ self.assertRaises(TypeError, rrsets.find, 1, RRClass.IN(), RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'), 1,
+ RRType.A())
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), 1)
+ self.assertRaises(TypeError, rrsets.find, Name('example'), RRType.A(),
+ RRClass.IN())
+
+ # Check bad arguments: too many/few arguments
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN(), RRType.A(), 0)
+ self.assertRaises(TypeError, rrsets.find, Name('example'),
+ RRClass.IN())
+
+ def test_add_remove_rrset(self):
+ name = Name('www.example.org')
+ rrclass = RRClass.IN()
+ rrtype = RRType.A()
+
+ # Create a collection with no RRsets
+ rrsets = RRsetCollection()
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # add the record, then it should be found
+ rrset = RRset(name, rrclass, rrtype, RRTTL(60))
+ rrset.add_rdata(Rdata(rrtype, rrclass, '192.0.2.1'))
+ self.assertEqual(None, rrsets.add_rrset(rrset))
+ self.check_find_result(rrsets)
+
+ # duplicate add is (at least currently) rejected
+ self.assertRaises(ValueError, rrsets.add_rrset, rrset)
+
+ # remove it, then we cannot find it any more.
+ self.assertTrue(rrsets.remove_rrset(name, rrclass, rrtype))
+ self.assertEqual(None, rrsets.find(name, rrclass, rrtype))
+
+ # duplicate remove (specified RRset doesn't exist) reulsts in False
+ self.assertFalse(rrsets.remove_rrset(name, rrclass, rrtype))
+
+ # Checking bad args
+ self.assertRaises(TypeError, rrsets.add_rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset, rrset, 1)
+ self.assertRaises(TypeError, rrsets.add_rrset)
+
+ self.assertRaises(TypeError, rrsets.remove_rrset, 1, rrclass, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, 1, rrtype)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass, 1)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrtype,
+ rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass)
+ self.assertRaises(TypeError, rrsets.remove_rrset, name, rrclass,
+ rrtype, 1)
+
+ def test_empty_class(self):
+ # A user defined collection class shouldn't cause disruption.
+ class EmptyRRsetCollection(RRsetCollectionBase):
+ def __init__(self):
+ pass
+ rrsets = EmptyRRsetCollection()
+ self.assertRaises(TypeError, rrsets.find, Name('www.example.org'),
+ RRClass.IN(), RRType.A())
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/tests/zone_checker_python_test.py b/src/lib/dns/python/tests/zone_checker_python_test.py
new file mode 100644
index 0000000..66b6c47
--- /dev/null
+++ b/src/lib/dns/python/tests/zone_checker_python_test.py
@@ -0,0 +1,179 @@
+# Copyright (C) 2013 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+import unittest
+import sys
+from pydnspp import *
+
+# A separate exception class raised from some tests to see if it's propagated.
+class FakeException(Exception):
+ pass
+
+class ZoneCheckerTest(unittest.TestCase):
+ def __callback(self, reason, reasons):
+ # Issue callback for check_zone(). It simply records the given reason
+ # string in the given list.
+ reasons.append(reason)
+
+ def test_check(self):
+ errors = []
+ warns = []
+
+ # A successful case with no warning.
+ rrsets = RRsetCollection(b'example.org. 0 SOA . . 0 0 0 0 0\n' +
+ b'example.org. 0 NS ns.example.org.\n' +
+ b'ns.example.org. 0 A 192.0.2.1\n',
+ Name('example.org'), RRClass.IN())
+ self.assertTrue(check_zone(Name('example.org'), RRClass.IN(),
+ rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ # Check fails and one additional warning.
+ rrsets = RRsetCollection(b'example.org. 0 NS ns.example.org.',
+ Name('example.org'), RRClass.IN())
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (lambda r: self.__callback(r, errors),
+ lambda r: self.__callback(r, warns))))
+ self.assertEqual(['zone example.org/IN: has 0 SOA records'], errors)
+ self.assertEqual(['zone example.org/IN: NS has no address records ' +
+ '(A or AAAA)'], warns)
+
+ # Same RRset collection, suppressing callbacks
+ errors = []
+ warns = []
+ self.assertFalse(check_zone(Name('example.org'), RRClass.IN(), rrsets,
+ (None, None)))
+ self.assertEqual([], errors)
+ self.assertEqual([], warns)
+
+ def test_check_badarg(self):
+ rrsets = RRsetCollection()
+ # Bad types
+ self.assertRaises(TypeError, check_zone, 1, RRClass.IN(), rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), 1, rrsets,
+ (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ 1, (None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, 1)
+
+ # Bad callbacks
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (1, None))
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, 1))
+
+ # Extra/missing args
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None), 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets)
+ check_zone(Name('example'), RRClass.IN(), rrsets, (None, None))
+
+ def test_check_callback_fail(self):
+ # Let the call raise a Python exception. It should be propagated to
+ # the top level.
+ def __bad_callback(reason):
+ raise FakeException('error in callback')
+
+ # Using an empty collection, triggering an error callback.
+ self.assertRaises(FakeException, check_zone, Name('example.org'),
+ RRClass.IN(), RRsetCollection(),
+ (__bad_callback, None))
+
+ # An unusual case: the callback is expected to return None, but if it
+ # returns an actual object it shouldn't cause leak inside the callback.
+ class RefChecker:
+ pass
+ def __callback(reason, checker):
+ return checker
+
+ ref_checker = RefChecker()
+ orig_refcnt = sys.getrefcount(ref_checker)
+ check_zone(Name('example.org'), RRClass.IN(), RRsetCollection(),
+ (lambda r: __callback(r, ref_checker), None))
+ self.assertEqual(orig_refcnt, sys.getrefcount(ref_checker))
+
+ def test_check_custom_collection(self):
+ # Test if check_zone() works with pure-Python RRsetCollection.
+
+ class FakeRRsetCollection(RRsetCollectionBase):
+ # This is the Python-only collection class. Its find() makes
+ # the check pass by default, by returning hardcoded RRsets.
+ # If raise_on_find is set to True, find() raises an exception.
+ # If find_result is set to something other than 'use_default'
+ # (as a string), find() returns that specified value (note that
+ # it can be None).
+
+ def __init__(self, raise_on_find=False, find_result='use_default'):
+ self.__raise_on_find = raise_on_find
+ self.__find_result = find_result
+
+ def find(self, name, rrclass, rrtype):
+ if self.__raise_on_find:
+ raise FakeException('find error')
+ if self.__find_result is not 'use_default':
+ return self.__find_result
+ if rrtype == RRType.SOA():
+ soa = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ soa.add_rdata(Rdata(RRType.SOA(), RRClass.IN(),
+ '. . 0 0 0 0 0'))
+ return soa
+ if rrtype == RRType.NS():
+ ns = RRset(Name('example'), RRClass.IN(), rrtype,
+ RRTTL(0))
+ ns.add_rdata(Rdata(RRType.NS(), RRClass.IN(),
+ 'example.org'))
+ return ns
+ return None
+
+ # A successful case. Just checking it works in that case.
+ rrsets = FakeRRsetCollection()
+ self.assertTrue(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Likewise, normal case but zone check fails.
+ rrsets = FakeRRsetCollection(False, None)
+ self.assertFalse(check_zone(Name('example'), RRClass.IN(), rrsets,
+ (None, None)))
+
+ # Our find() returns a bad type of result.
+ rrsets = FakeRRsetCollection(False, 1)
+ self.assertRaises(TypeError, check_zone, Name('example'), RRClass.IN(),
+ rrsets, (None, None))
+
+ # Our find() returns an empty SOA RRset. C++ zone checker code
+ # throws, which results in IscException.
+ rrsets = FakeRRsetCollection(False, RRset(Name('example'),
+ RRClass.IN(),
+ RRType.SOA(), RRTTL(0)))
+ self.assertRaises(IscException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+ # Our find() raises an exception. That exception is propagated to
+ # the top level.
+ rrsets = FakeRRsetCollection(True)
+ self.assertRaises(FakeException, check_zone, Name('example'),
+ RRClass.IN(), rrsets, (None, None))
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/src/lib/dns/python/zone_checker_python.cc b/src/lib/dns/python/zone_checker_python.cc
new file mode 100644
index 0000000..eaad72d
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.cc
@@ -0,0 +1,229 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+// Enable this if you use s# variants with PyArg_ParseTuple(), see
+// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
+#define PY_SSIZE_T_CLEAN
+
+// Python.h needs to be placed at the head of the program file, see:
+// http://docs.python.org/py3k/extending/extending.html#a-simple-example
+#include <Python.h>
+
+#include <util/python/pycppwrapper_util.h>
+
+#include <dns/python/name_python.h>
+#include <dns/python/rrclass_python.h>
+#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_python.h>
+#include <dns/python/rrset_collection_python.h>
+#include <dns/python/zone_checker_python.h>
+#include <dns/python/pydnspp_common.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+#include <dns/zone_checker.h>
+
+#include <boost/bind.hpp>
+
+#include <cstring>
+#include <string>
+#include <stdexcept>
+
+using std::string;
+using isc::util::python::PyObjectContainer;
+using namespace isc::dns;
+
+namespace {
+// This is a template for a common pattern of type mismatch error handling,
+// provided to save typing and repeating the mostly identical patterns.
+PyObject*
+setTypeError(PyObject* pobj, const char* var_name, const char* type_name) {
+ PyErr_Format(PyExc_TypeError, "%s must be a %s, not %.200s",
+ var_name, type_name, pobj->ob_type->tp_name);
+ return (NULL);
+}
+}
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+// Place the exception class in a named namespace to avoid weird run time
+// failure with clang++. See isc.log Python wrapper.
+namespace clang_unnamed_namespace_workaround {
+// This is used to abort check_zone() and go back to the top level.
+// We use a separate exception so it won't be caught in the middle.
+class InternalException : public std::exception {
+};
+}
+using namespace clang_unnamed_namespace_workaround;
+
+namespace {
+// This is a "wrapper" RRsetCollection subclass. It's constructed with
+// a Python RRsetCollection object, and its find() calls the Python version
+// of RRsetCollection.find(). This way, the check_zone() wrapper will work
+// for pure-Python RRsetCollection classes, too.
+class PyRRsetCollection : public RRsetCollectionBase {
+public:
+ PyRRsetCollection(PyObject* po_rrsets) : po_rrsets_(po_rrsets) {}
+
+ virtual ConstRRsetPtr find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const {
+ try {
+ // Convert C++ args to Python objects, and builds argument tuple
+ // to the Python method. This should basically succeed.
+ PyObjectContainer poc_name(createNameObject(name));
+ PyObjectContainer poc_rrclass(createRRClassObject(rrclass));
+ PyObjectContainer poc_rrtype(createRRTypeObject(rrtype));
+ PyObjectContainer poc_args(Py_BuildValue("(OOOO)",
+ po_rrsets_,
+ poc_name.get(),
+ poc_rrclass.get(),
+ poc_rrtype.get()));
+
+ // Call the Python method.
+ // PyObject_CallMethod is dirty and requires mutable C-string for
+ // method name and arguments. While it's unlikely for these to
+ // be modified, we err on the side of caution and make copies.
+ char method_name[sizeof("find")];
+ char method_args[sizeof("(OOO)")];
+ std::strcpy(method_name, "find");
+ std::strcpy(method_args, "(OOO)");
+ PyObjectContainer poc_result(
+ PyObject_CallMethod(po_rrsets_, method_name, method_args,
+ poc_name.get(), poc_rrclass.get(),
+ poc_rrtype.get()));
+ PyObject* const po_result = poc_result.get();
+ if (po_result == Py_None) {
+ return (ConstRRsetPtr());
+ } else if (PyRRset_Check(po_result)) {
+ return (PyRRset_ToRRsetPtr(po_result));
+ } else {
+ PyErr_SetString(PyExc_TypeError, "invalid type for "
+ "RRsetCollection.find(): must be None "
+ "or RRset");
+ throw InternalException();
+ }
+ } catch (const isc::util::python::PyCPPWrapperException& ex) {
+ // This normally means the method call fails. Propagate the
+ // already-set Python error to the top level. Other C++ exceptions
+ // are really unexpected, so we also (implicitly) propagate it
+ // to the top level and recognize it as "unexpected failure".
+ throw InternalException();
+ }
+ }
+
+ virtual IterPtr getBeginning() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+ virtual IterPtr getEnd() {
+ isc_throw(NotImplemented, "iterator support is not yet available");
+ }
+
+private:
+ PyObject* const po_rrsets_;
+};
+
+void
+callback(const string& reason, PyObject* obj) {
+ PyObjectContainer poc_args(Py_BuildValue("(s#)", reason.c_str(),
+ reason.size()));
+ PyObject* po_result = PyObject_CallObject(obj, poc_args.get());
+ if (po_result == NULL) {
+ throw InternalException();
+ }
+ Py_DECREF(po_result);
+}
+
+ZoneCheckerCallbacks::IssueCallback
+PyCallable_ToCallback(PyObject* obj) {
+ if (obj == Py_None) {
+ return (NULL);
+ }
+ return (boost::bind(callback, _1, obj));
+}
+
+}
+
+PyObject*
+pyCheckZone(PyObject*, PyObject* args) {
+ try {
+ PyObject* po_name;
+ PyObject* po_rrclass;
+ PyObject* po_rrsets;
+ PyObject* po_error;
+ PyObject* po_warn;
+
+ if (PyArg_ParseTuple(args, "OOO(OO)", &po_name, &po_rrclass,
+ &po_rrsets, &po_error, &po_warn)) {
+ if (!PyName_Check(po_name)) {
+ return (setTypeError(po_name, "zone_name", "Name"));
+ }
+ if (!PyRRClass_Check(po_rrclass)) {
+ return (setTypeError(po_rrclass, "zone_rrclass", "RRClass"));
+ }
+ if (!PyObject_TypeCheck(po_rrsets, &rrset_collection_base_type)) {
+ return (setTypeError(po_rrsets, "zone_rrsets",
+ "RRsetCollectionBase"));
+ }
+ if (po_error != Py_None && PyCallable_Check(po_error) == 0) {
+ return (setTypeError(po_error, "error", "callable or None"));
+ }
+ if (po_warn != Py_None && PyCallable_Check(po_warn) == 0) {
+ return (setTypeError(po_warn, "warn", "callable or None"));
+ }
+
+ PyRRsetCollection py_rrsets(po_rrsets);
+ if (checkZone(PyName_ToName(po_name),
+ PyRRClass_ToRRClass(po_rrclass), py_rrsets,
+ ZoneCheckerCallbacks(
+ PyCallable_ToCallback(po_error),
+ PyCallable_ToCallback(po_warn)))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ }
+ } catch (const InternalException& ex) {
+ // Normally, error string should have been set already. For some
+ // rare cases such as memory allocation failure, we set the last-resort
+ // error string.
+ if (PyErr_Occurred() == NULL) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in check_zone()");
+ }
+ return (NULL);
+ } catch (const std::exception& ex) {
+ const string ex_what = "Unexpected failure in check_zone(): " +
+ string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
+ return (NULL);
+ }
+
+ return (NULL);
+}
+
+} // namespace internal
+} // namespace python
+} // namespace dns
+} // namespace isc
diff --git a/src/lib/dns/python/zone_checker_python.h b/src/lib/dns/python/zone_checker_python.h
new file mode 100644
index 0000000..63168fd
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python.h
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef PYTHON_ZONE_CHECKER_H
+#define PYTHON_ZONE_CHECKER_H 1
+
+#include <Python.h>
+
+namespace isc {
+namespace dns {
+namespace python {
+namespace internal {
+
+PyObject* pyCheckZone(PyObject* self, PyObject* args);
+
+} // namespace python
+} // namespace python
+} // namespace dns
+} // namespace isc
+#endif // PYTHON_ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/python/zone_checker_python_inc.cc b/src/lib/dns/python/zone_checker_python_inc.cc
new file mode 100644
index 0000000..c99042e
--- /dev/null
+++ b/src/lib/dns/python/zone_checker_python_inc.cc
@@ -0,0 +1,79 @@
+namespace {
+// Modifications
+// - callbacks => (error, warn)
+// - recover paragraph before itemization (it's a bug of convert script)
+// - correct broken format for nested items (another bug of script)
+// - true/false => True/False
+// - removed Exception section (for simplicity)
+const char* const dns_checkZone_doc = "\
+check_zone(zone_name, zone_class, zone_rrsets, (error, warn)) -> bool\n\
+\n\
+Perform basic integrity checks on zone RRsets.\n\
+\n\
+This function performs some lightweight checks on zone's SOA and\n\
+(apex) NS records. Here, lightweight means it doesn't require\n\
+traversing the entire zone, and should be expected to complete\n\
+reasonably quickly regardless of the size of the zone.\n\
+\n\
+It distinguishes \"critical\" errors and other undesirable issues: the\n\
+former should be interpreted as the resulting zone shouldn't be used\n\
+further, e.g, by an authoritative server implementation; the latter\n\
+means the issues are better to be addressed but are not necessarily\n\
+considered to make the zone invalid. Critical errors are reported via\n\
+the error() function, and non critical issues are reported via warn().\n\
+\n\
+Specific checks performed by this function is as follows. Failure of\n\
+a check is considered a critical error unless noted otherwise:\n\
+\n\
+- There is exactly one SOA RR at the zone apex.\n\
+- There is at least one NS RR at the zone apex.\n\
+- For each apex NS record, if the NS name (the RDATA of the record) is\n\
+ in the zone (i.e., it's a subdomain of the zone origin and above any\n\
+ zone cut due to delegation), check the following:\n\
+ - the NS name should have an address record (AAAA or A). Failure of\n\
+ this check is considered a non critical issue.\n\
+ - the NS name does not have a CNAME. This is prohibited by Section\n\
+ 10.3 of RFC 2181.\n\
+ - the NS name is not subject to DNAME substitution. This is prohibited\n\
+ by Section 4 of RFC 6672.\n\
+\n\
+In addition, when the check is completed without any\n\
+critical error, this function guarantees that RRsets for the SOA and\n\
+(apex) NS stored in the passed RRset collection have the expected\n\
+type of Rdata objects, i.e., generic.SOA and generic.NS,\n\
+respectively. (This is normally expected to be the case, but not\n\
+guaranteed by the API).\n\
+\n\
+As for the check on the existence of AAAA or A records for NS names,\n\
+it should be noted that BIND 9 treats this as a critical error. It's\n\
+not clear whether it's an implementation dependent behavior or based\n\
+on the protocol standard (it looks like the former), but to make it\n\
+sure we need to confirm there is even no wildcard match for the names.\n\
+This should be a very rare configuration, and more expensive to\n\
+detect, so we do not check this condition, and treat this case as a\n\
+non critical issue.\n\
+\n\
+This function indicates the result of the checks (whether there is a\n\
+critical error) via the return value: It returns True if there is no\n\
+critical error and returns False otherwise. It doesn't throw an\n\
+exception on encountering an error so that it can report as many\n\
+errors as possible in a single call. If an exception is a better way\n\
+to signal the error, the caller can pass a callable object as error()\n\
+that throws.\n\
+\n\
+This function can still throw an exception if it finds a really bogus\n\
+condition that is most likely to be an implementation bug of the\n\
+caller. Such cases include when an RRset contained in the RRset\n\
+collection is empty.\n\
+\n\
+Parameters:\n\
+ zone_name The name of the zone to be checked\n\
+ zone_class The RR class of the zone to be checked\n\
+ zone_rrsets The collection of RRsets of the zone\n\
+ error Callable object used to report errors\n\
+ warn Callable object used to report non-critical issues\n\
+\n\
+Return Value(s): True if no critical errors are found; False\n\
+otherwise.\n\
+";
+} // unnamed namespace
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 4cd63cc..3fe0c74 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -60,7 +60,7 @@ public:
///
/// \brief A standard DNS module exception that is thrown if RDATA parser
-/// parser encounters a character-string (as defined in RFC1035) exceeding
+/// encounters a character-string (as defined in RFC1035) exceeding
/// the maximum allowable length (\c MAX_CHARSTRING_LEN).
///
class CharStringTooLong : public Exception {
diff --git a/src/lib/dns/rdata/generic/detail/char_string.cc b/src/lib/dns/rdata/generic/detail/char_string.cc
index fb4c9b4..4c8965a 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.cc
+++ b/src/lib/dns/rdata/generic/detail/char_string.cc
@@ -14,9 +14,11 @@
#include <exceptions/exceptions.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/master_lexer.h>
#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
#include <boost/lexical_cast.hpp>
@@ -57,8 +59,8 @@ decimalToNumber(const char* s, const char* s_end) {
}
void
-strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result)
+stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result)
{
// make a space for the 1-byte length field; filled in at the end
result.push_back(0);
@@ -91,6 +93,83 @@ strToCharString(const MasterToken::StringRegion& str_region,
result[0] = result.size() - 1;
}
+std::string
+charStringToString(const CharString& char_string) {
+ std::string s;
+ for (CharString::const_iterator it = char_string.begin() + 1;
+ it != char_string.end(); ++it) {
+ const uint8_t ch = *it;
+ if ((ch < 0x20) || (ch >= 0x7f)) {
+ // convert to escaped \xxx (decimal) format
+ s.push_back('\\');
+ s.push_back('0' + ((ch / 100) % 10));
+ s.push_back('0' + ((ch / 10) % 10));
+ s.push_back('0' + (ch % 10));
+ continue;
+ }
+ if ((ch == '"') || (ch == ';') || (ch == '\\')) {
+ s.push_back('\\');
+ }
+ s.push_back(ch);
+ }
+
+ return (s);
+}
+
+int compareCharStrings(const detail::CharString& self,
+ const detail::CharString& other) {
+ if (self.size() == 0 && other.size() == 0) {
+ return (0);
+ }
+ if (self.size() == 0) {
+ return (-1);
+ }
+ if (other.size() == 0) {
+ return (1);
+ }
+ const size_t self_len = self[0];
+ const size_t other_len = other[0];
+ const size_t cmp_len = std::min(self_len, other_len);
+ const int cmp = std::memcmp(&self[1], &other[1], cmp_len);
+ if (cmp < 0) {
+ return (-1);
+ } else if (cmp > 0) {
+ return (1);
+ } else if (self_len < other_len) {
+ return (-1);
+ } else if (self_len > other_len) {
+ return (1);
+ } else {
+ return (0);
+ }
+}
+
+size_t
+bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target) {
+ if (rdata_len < 1 || buffer.getLength() - buffer.getPosition() < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "insufficient data to read character-string length");
+ }
+ const uint8_t len = buffer.readUint8();
+ if (rdata_len < len + 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "character string length is too large: " <<
+ static_cast<int>(len));
+ }
+ if (buffer.getLength() - buffer.getPosition() < len) {
+ isc_throw(isc::dns::DNSMessageFORMERR,
+ "not enough data in buffer to read character-string of len"
+ << static_cast<int>(len));
+ }
+
+ target.resize(len + 1);
+ target[0] = len;
+ buffer.readData(&target[0] + 1, len);
+
+ return (len + 1);
+}
+
} // end of detail
} // end of generic
} // end of rdata
diff --git a/src/lib/dns/rdata/generic/detail/char_string.h b/src/lib/dns/rdata/generic/detail/char_string.h
index 702af04..8e3e294 100644
--- a/src/lib/dns/rdata/generic/detail/char_string.h
+++ b/src/lib/dns/rdata/generic/detail/char_string.h
@@ -17,7 +17,9 @@
#include <dns/master_lexer.h>
+#include <string>
#include <vector>
+#include <algorithm>
#include <stdint.h>
namespace isc {
@@ -48,8 +50,54 @@ typedef std::vector<uint8_t> CharString;
/// \brief str_region A string that represents a character-string.
/// \brief result A placeholder vector where the resulting data are to be
/// stored. Expected to be empty, but it's not checked.
-void strToCharString(const MasterToken::StringRegion& str_region,
- CharString& result);
+void stringToCharString(const MasterToken::StringRegion& str_region,
+ CharString& result);
+
+/// \brief Convert a CharString into a textual DNS character-string.
+///
+/// This method converts a binary 8-bit representation of a DNS
+/// character string into a textual string representation, escaping any
+/// special characters in the process. For example, characters like
+/// double-quotes, semi-colon and backspace are prefixed with backspace
+/// character, and characters not in the printable range of [0x20, 0x7e]
+/// (inclusive) are converted to the \xxx 3-digit decimal
+/// representation.
+///
+/// \param char_string The \c CharString to convert.
+/// \return A string representation of \c char_string.
+std::string charStringToString(const CharString& char_string);
+
+/// \brief Compare two CharString objects
+///
+/// \param self The CharString field to compare
+/// \param other The CharString field to compare to
+///
+/// \return -1 if \c self would be sorted before \c other
+/// 1 if \c self would be sorted after \c other
+/// 0 if \c self and \c other are equal
+int compareCharStrings(const CharString& self, const CharString& other);
+
+/// \brief Convert a buffer containing a character-string to CharString
+///
+/// This method reads one character-string from the given buffer (in wire
+/// format) and places the result in the given \c CharString object.
+/// Since this is expected to be used in message parsing, the exception it
+/// raises is of that type.
+///
+/// On success, the buffer position is advanced to the end of the char-string,
+/// and the number of bytes read is returned.
+///
+/// \param buffer The buffer to read from.
+/// \param rdata_len The total size of the rr's rdata currently being read
+/// (used for integrity checks in the wire data)
+/// \param target The \c CharString where the result will be stored. Any
+/// existing data in the target will be overwritten.
+/// \throw DNSMessageFORMERR If the available data is not enough to read
+/// the character-string, or if the character-string length is out of bounds
+/// \return The number of bytes read
+size_t bufferToCharString(isc::util::InputBuffer& buffer, size_t rdata_len,
+ CharString& target);
+
} // namespace detail
} // namespace generic
diff --git a/src/lib/dns/rdata/generic/detail/lexer_util.h b/src/lib/dns/rdata/generic/detail/lexer_util.h
new file mode 100644
index 0000000..89df5a0
--- /dev/null
+++ b/src/lib/dns/rdata/generic/detail/lexer_util.h
@@ -0,0 +1,70 @@
+// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef DNS_RDATA_LEXER_UTIL_H
+#define DNS_RDATA_LEXER_UTIL_H 1
+
+#include <dns/name.h>
+#include <dns/master_lexer.h>
+
+/// \file lexer_util.h
+/// \brief Utilities for extracting RDATA fields from lexer.
+///
+/// This file intends to define convenient small routines that can be
+/// commonly used in the RDATA implementation to build RDATA fields from
+/// a \c MasterLexer.
+
+namespace isc {
+namespace dns {
+namespace rdata {
+namespace generic {
+namespace detail {
+
+/// \brief Construct a Name object using a master lexer and optional origin.
+///
+/// This is a convenient shortcut of commonly used code pattern that would
+/// be used to build RDATA that contain a domain name field.
+///
+/// Note that this function throws an exception against invalid input.
+/// The (direct or indirect) caller's responsibility needs to expect and
+/// handle exceptions appropriately.
+///
+/// \throw MasterLexer::LexerError The next token from lexer is not string.
+/// \throw Other Exceptions from the \c Name class constructor if the next
+/// string token from the lexer does not represent a valid name.
+///
+/// \param lexer A \c MasterLexer object. Its next token is expected to be
+/// a string that represent a domain name.
+/// \param origin If non NULL, specifies the origin of the name to be
+/// constructed.
+///
+/// \return A new Name object that corresponds to the next string token of
+/// the \c lexer.
+inline Name
+createNameFromLexer(MasterLexer& lexer, const Name* origin) {
+ const MasterToken::StringRegion& str_region =
+ lexer.getNextToken(MasterToken::STRING).getStringRegion();
+ return (Name(str_region.beg, str_region.len, origin));
+}
+
+} // namespace detail
+} // namespace generic
+} // namespace rdata
+} // namespace dns
+} // namespace isc
+#endif // DNS_RDATA_LEXER_UTIL_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/generic/detail/txt_like.h b/src/lib/dns/rdata/generic/detail/txt_like.h
index d1916e3..b48d109 100644
--- a/src/lib/dns/rdata/generic/detail/txt_like.h
+++ b/src/lib/dns/rdata/generic/detail/txt_like.h
@@ -119,7 +119,7 @@ private:
break;
}
string_list_.push_back(std::vector<uint8_t>());
- strToCharString(token.getStringRegion(), string_list_.back());
+ stringToCharString(token.getStringRegion(), string_list_.back());
}
// Let upper layer handle eol/eof.
@@ -177,18 +177,14 @@ public:
toText() const {
std::string s;
- // XXX: this implementation is not entirely correct. for example, it
- // should escape double-quotes if they appear in the character string.
for (std::vector<std::vector<uint8_t> >::const_iterator it =
- string_list_.begin();
- it != string_list_.end();
- ++it)
+ string_list_.begin(); it != string_list_.end(); ++it)
{
if (!s.empty()) {
s.push_back(' ');
}
s.push_back('"');
- s.insert(s.end(), (*it).begin() + 1, (*it).end());
+ s.append(charStringToString(*it));
s.push_back('"');
}
diff --git a/src/lib/dns/rdata/generic/hinfo_13.cc b/src/lib/dns/rdata/generic/hinfo_13.cc
index 77bfdfd..319ec7d 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.cc
+++ b/src/lib/dns/rdata/generic/hinfo_13.cc
@@ -14,66 +14,105 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
-
-#include <dns/name.h>
-#include <dns/messagerenderer.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
-#include <dns/character_string.h>
-#include <util/strutil.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
using namespace std;
-using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl {
+public:
+ HINFOImpl(const std::string& hinfo_str) {
+ std::istringstream ss(hinfo_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseHINFOData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid HINFO text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct HINFO RDATA from "
+ << hinfo_str << "': " << ex.what());
+ }
+ }
-HINFO::HINFO(const std::string& hinfo_str) {
- string::const_iterator input_iterator = hinfo_str.begin();
+ HINFOImpl(InputBuffer& buffer, size_t rdata_len) {
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, cpu);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, os);
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "HINFO RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
+ }
- bool quoted;
- cpu_ = getNextCharacterString(hinfo_str, input_iterator, "ed);
+ HINFOImpl(MasterLexer& lexer)
+ {
+ parseHINFOData(lexer);
+ }
- skipLeftSpaces(hinfo_str, input_iterator, quoted);
+private:
+ void
+ parseHINFOData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), cpu);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), os);
+ }
- os_ = getNextCharacterString(hinfo_str, input_iterator);
+public:
+ detail::CharString cpu;
+ detail::CharString os;
+};
- // Skip whitespace at the end.
- while (input_iterator < hinfo_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
- if (input_iterator < hinfo_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: too many fields.");
- }
-}
+HINFO::HINFO(const std::string& hinfo_str) : impl_(new HINFOImpl(hinfo_str))
+{}
-HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) {
- cpu_ = getNextCharacterString(buffer, rdata_len);
- os_ = getNextCharacterString(buffer, rdata_len);
-}
+
+HINFO::HINFO(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new HINFOImpl(buffer, rdata_len))
+{}
HINFO::HINFO(const HINFO& source):
- Rdata(), cpu_(source.cpu_), os_(source.os_)
+ Rdata(), impl_(new HINFOImpl(*source.impl_))
+{
+}
+
+HINFO::HINFO(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new HINFOImpl(lexer))
+{}
+
+HINFO&
+HINFO::operator=(const HINFO& source)
{
+ impl_.reset(new HINFOImpl(*source.impl_));
+ return (*this);
+}
+
+HINFO::~HINFO() {
}
std::string
HINFO::toText() const {
string result;
result += "\"";
- result += cpu_;
+ result += detail::charStringToString(impl_->cpu);
result += "\" \"";
- result += os_;
+ result += detail::charStringToString(impl_->os);
result += "\"";
return (result);
}
@@ -92,49 +131,28 @@ int
HINFO::compare(const Rdata& other) const {
const HINFO& other_hinfo = dynamic_cast<const HINFO&>(other);
- if (cpu_ < other_hinfo.cpu_) {
- return (-1);
- } else if (cpu_ > other_hinfo.cpu_) {
- return (1);
- }
-
- if (os_ < other_hinfo.os_) {
- return (-1);
- } else if (os_ > other_hinfo.os_) {
- return (1);
+ const int cmp = compareCharStrings(impl_->cpu, other_hinfo.impl_->cpu);
+ if (cmp != 0) {
+ return (cmp);
}
-
- return (0);
+ return (compareCharStrings(impl_->os, other_hinfo.impl_->os));
}
-const std::string&
+const std::string
HINFO::getCPU() const {
- return (cpu_);
+ return (detail::charStringToString(impl_->cpu));
}
-const std::string&
+const std::string
HINFO::getOS() const {
- return (os_);
+ return (detail::charStringToString(impl_->os));
}
+template <typename T>
void
-HINFO::skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool optional)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: field is missing.");
- }
-
- if (!isspace(*input_iterator) && !optional) {
- isc_throw(InvalidRdataText,
- "Invalid HINFO text format: fields are not separated by space.");
- }
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
+HINFO::toWireHelper(T& outputer) const {
+ outputer.writeData(&impl_->cpu[0], impl_->cpu.size());
+ outputer.writeData(&impl_->os[0], impl_->os.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/hinfo_13.h b/src/lib/dns/rdata/generic/hinfo_13.h
index 5534a7e..60404c9 100644
--- a/src/lib/dns/rdata/generic/hinfo_13.h
+++ b/src/lib/dns/rdata/generic/hinfo_13.h
@@ -17,6 +17,9 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -28,6 +31,8 @@
// BEGIN_RDATA_NAMESPACE
+class HINFOImpl;
+
/// \brief \c HINFO class represents the HINFO rdata defined in
/// RFC1034, RFC1035
///
@@ -40,38 +45,21 @@ public:
// END_COMMON_MEMBERS
// HINFO specific methods
- const std::string& getCPU() const;
- const std::string& getOS() const;
+ ~HINFO();
-private:
- /// Skip the left whitespaces of the input string
- ///
- /// If \c optional argument is \c true and no spaces occur at the
- /// current location, then nothing happens. If \c optional is
- /// \c false and no spaces occur at the current location, then
- /// the \c InvalidRdataText exception is thrown.
- ///
- /// \param input_str The input string
- /// \param input_iterator From which the skipping started
- /// \param optional If true, the spaces are optionally skipped.
- void skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator,
- bool optional);
+ HINFO& operator=(const HINFO&);
+
+ const std::string getCPU() const;
+ const std::string getOS() const;
+private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint8(cpu_.size());
- outputer.writeData(cpu_.c_str(), cpu_.size());
-
- outputer.writeUint8(os_.size());
- outputer.writeData(os_.c_str(), os_.size());
- }
+ void toWireHelper(T& outputer) const;
- std::string cpu_;
- std::string os_;
+ boost::scoped_ptr<HINFOImpl> impl_;
};
diff --git a/src/lib/dns/rdata/generic/naptr_35.cc b/src/lib/dns/rdata/generic/naptr_35.cc
index 0958eff..78bf472 100644
--- a/src/lib/dns/rdata/generic/naptr_35.cc
+++ b/src/lib/dns/rdata/generic/naptr_35.cc
@@ -14,119 +14,147 @@
#include <config.h>
-#include <string>
-
-#include <boost/lexical_cast.hpp>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/character_string.h>
#include <dns/name.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/char_string.h>
+#include <exceptions/exceptions.h>
+
+#include <string>
+#include <boost/lexical_cast.hpp>
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
using namespace isc::dns;
-using namespace isc::dns::characterstr;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
-namespace {
-/// Skip the left whitespaces of the input string
-///
-/// \param input_str The input string
-/// \param input_iterator From which the skipping started
-void
-skipLeftSpaces(const std::string& input_str,
- std::string::const_iterator& input_iterator)
-{
- if (input_iterator >= input_str.end()) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, field is missing.");
+class NAPTRImpl {
+public:
+ NAPTRImpl() : replacement(".") {}
+
+ NAPTRImpl(InputBuffer& buffer, size_t rdata_len) : replacement(".") {
+ if (rdata_len < 4 || buffer.getLength() - buffer.getPosition() < 4) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: insufficient length ");
+ }
+ order = buffer.readUint16();
+ preference = buffer.readUint16();
+ rdata_len -= 4;
+
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, flags);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, services);
+ rdata_len -= detail::bufferToCharString(buffer, rdata_len, regexp);
+ replacement = Name(buffer);
+ if (rdata_len < 1) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing "
+ "NAPTR RDATA wire format: missing replacement name");
+ }
+ rdata_len -= replacement.getLength();
+
+ if (rdata_len != 0) {
+ isc_throw(isc::dns::DNSMessageFORMERR, "Error in parsing " <<
+ "NAPTR RDATA: bytes left at end: " <<
+ static_cast<int>(rdata_len));
+ }
}
- if (!isspace(*input_iterator)) {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, fields are not separated by space.");
+ NAPTRImpl(const std::string& naptr_str) : replacement(".") {
+ std::istringstream ss(naptr_str);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ try {
+ parseNAPTRData(lexer);
+ // Should be at end of data now
+ if (lexer.getNextToken(MasterToken::QSTRING, true).getType() !=
+ MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: too many fields.");
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct NAPTR RDATA from "
+ << naptr_str << "': " << ex.what());
+ }
}
- // Skip white spaces
- while (input_iterator < input_str.end() && isspace(*input_iterator)) {
- ++input_iterator;
- }
-}
-} // Anonymous namespace
+ NAPTRImpl(MasterLexer& lexer) : replacement(".")
+ {
+ parseNAPTRData(lexer);
+ }
-NAPTR::NAPTR(InputBuffer& buffer, size_t len):
- replacement_(".")
-{
- order_ = buffer.readUint16();
- preference_ = buffer.readUint16();
+private:
+ void
+ parseNAPTRData(MasterLexer& lexer) {
+ MasterToken token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: order out of range: "
+ << token.getNumber());
+ }
+ order = token.getNumber();
+ token = lexer.getNextToken(MasterToken::NUMBER);
+ if (token.getNumber() > 65535) {
+ isc_throw(InvalidRdataText,
+ "Invalid NAPTR text format: preference out of range: "
+ << token.getNumber());
+ }
+ preference = token.getNumber();
+
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), flags);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), services);
+ token = lexer.getNextToken(MasterToken::QSTRING);
+ stringToCharString(token.getStringRegion(), regexp);
+
+ token = lexer.getNextToken(MasterToken::STRING);
+ replacement = Name(token.getString());
+ }
- flags_ = getNextCharacterString(buffer, len);
- services_ = getNextCharacterString(buffer, len);
- regexp_ = getNextCharacterString(buffer, len);
- replacement_ = Name(buffer);
-}
-NAPTR::NAPTR(const std::string& naptr_str):
- replacement_(".")
-{
- istringstream iss(naptr_str);
+public:
uint16_t order;
uint16_t preference;
-
- iss >> order >> preference;
-
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid NAPTR text format");
- }
-
- order_ = order;
- preference_ = preference;
-
- string::const_iterator input_iterator = naptr_str.begin() + iss.tellg();
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- flags_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- services_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- regexp_ = getNextCharacterString(naptr_str, input_iterator);
-
- skipLeftSpaces(naptr_str, input_iterator);
-
- if (input_iterator < naptr_str.end()) {
- string replacementStr(input_iterator, naptr_str.end());
-
- replacement_ = Name(replacementStr);
- } else {
- isc_throw(InvalidRdataText,
- "Invalid NAPTR text format, replacement field is missing");
- }
+ detail::CharString flags;
+ detail::CharString services;
+ detail::CharString regexp;
+ Name replacement;
+};
+
+NAPTR::NAPTR(InputBuffer& buffer, size_t rdata_len) :
+ impl_(new NAPTRImpl(buffer, rdata_len))
+{}
+
+NAPTR::NAPTR(const std::string& naptr_str) : impl_(new NAPTRImpl(naptr_str))
+{}
+
+NAPTR::NAPTR(MasterLexer& lexer, const Name*,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ impl_(new NAPTRImpl(lexer))
+{}
+
+NAPTR::NAPTR(const NAPTR& naptr) : Rdata(),
+ impl_(new NAPTRImpl(*naptr.impl_))
+{}
+
+NAPTR&
+NAPTR::operator=(const NAPTR& source)
+{
+ impl_.reset(new NAPTRImpl(*source.impl_));
+ return (*this);
}
-NAPTR::NAPTR(const NAPTR& naptr):
- Rdata(), order_(naptr.order_), preference_(naptr.preference_),
- flags_(naptr.flags_), services_(naptr.services_), regexp_(naptr.regexp_),
- replacement_(naptr.replacement_)
-{
+NAPTR::~NAPTR() {
}
void
NAPTR::toWire(OutputBuffer& buffer) const {
toWireHelper(buffer);
- replacement_.toWire(buffer);
-
+ impl_->replacement.toWire(buffer);
}
void
@@ -134,23 +162,23 @@ NAPTR::toWire(AbstractMessageRenderer& renderer) const {
toWireHelper(renderer);
// Type NAPTR is not "well-known", and name compression must be disabled
// per RFC3597.
- renderer.writeName(replacement_, false);
+ renderer.writeName(impl_->replacement, false);
}
string
NAPTR::toText() const {
string result;
- result += lexical_cast<string>(order_);
+ result += lexical_cast<string>(impl_->order);
result += " ";
- result += lexical_cast<string>(preference_);
+ result += lexical_cast<string>(impl_->preference);
result += " \"";
- result += flags_;
+ result += detail::charStringToString(impl_->flags);
result += "\" \"";
- result += services_;
+ result += detail::charStringToString(impl_->services);
result += "\" \"";
- result += regexp_;
+ result += detail::charStringToString(impl_->regexp);
result += "\" ";
- result += replacement_.toText();
+ result += impl_->replacement.toText();
return (result);
}
@@ -158,67 +186,78 @@ int
NAPTR::compare(const Rdata& other) const {
const NAPTR other_naptr = dynamic_cast<const NAPTR&>(other);
- if (order_ < other_naptr.order_) {
+ if (impl_->order < other_naptr.impl_->order) {
return (-1);
- } else if (order_ > other_naptr.order_) {
+ } else if (impl_->order > other_naptr.impl_->order) {
return (1);
}
- if (preference_ < other_naptr.preference_) {
+ if (impl_->preference < other_naptr.impl_->preference) {
return (-1);
- } else if (preference_ > other_naptr.preference_) {
+ } else if (impl_->preference > other_naptr.impl_->preference) {
return (1);
}
- if (flags_ < other_naptr.flags_) {
- return (-1);
- } else if (flags_ > other_naptr.flags_) {
- return (1);
+ const int fcmp = detail::compareCharStrings(impl_->flags,
+ other_naptr.impl_->flags);
+ if (fcmp != 0) {
+ return (fcmp);
}
- if (services_ < other_naptr.services_) {
- return (-1);
- } else if (services_ > other_naptr.services_) {
- return (1);
+ const int scmp = detail::compareCharStrings(impl_->services,
+ other_naptr.impl_->services);
+ if (scmp != 0) {
+ return (scmp);
}
- if (regexp_ < other_naptr.regexp_) {
- return (-1);
- } else if (regexp_ > other_naptr.regexp_) {
- return (1);
+ const int rcmp = detail::compareCharStrings(impl_->regexp,
+ other_naptr.impl_->regexp);
+ if (rcmp != 0) {
+ return (rcmp);
}
- return (compareNames(replacement_, other_naptr.replacement_));
+ return (compareNames(impl_->replacement, other_naptr.impl_->replacement));
}
uint16_t
NAPTR::getOrder() const {
- return (order_);
+ return (impl_->order);
}
uint16_t
NAPTR::getPreference() const {
- return (preference_);
+ return (impl_->preference);
}
-const std::string&
+const std::string
NAPTR::getFlags() const {
- return (flags_);
+ return (detail::charStringToString(impl_->flags));
}
-const std::string&
+const std::string
NAPTR::getServices() const {
- return (services_);
+ return (detail::charStringToString(impl_->services));
}
-const std::string&
+const std::string
NAPTR::getRegexp() const {
- return (regexp_);
+ return (detail::charStringToString(impl_->regexp));
}
const Name&
NAPTR::getReplacement() const {
- return (replacement_);
+ return (impl_->replacement);
+}
+
+template <typename T>
+void
+NAPTR::toWireHelper(T& outputer) const {
+ outputer.writeUint16(impl_->order);
+ outputer.writeUint16(impl_->preference);
+
+ outputer.writeData(&impl_->flags[0], impl_->flags.size());
+ outputer.writeData(&impl_->services[0], impl_->services.size());
+ outputer.writeData(&impl_->regexp[0], impl_->regexp.size());
}
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/naptr_35.h b/src/lib/dns/rdata/generic/naptr_35.h
index c82ef87..a2e2cae 100644
--- a/src/lib/dns/rdata/generic/naptr_35.h
+++ b/src/lib/dns/rdata/generic/naptr_35.h
@@ -16,6 +16,8 @@
#include <string>
+#include <boost/scoped_ptr.hpp>
+
#include <dns/name.h>
#include <dns/rdata.h>
#include <util/buffer.h>
@@ -27,6 +29,8 @@
// BEGIN_RDATA_NAMESPACE
+class NAPTRImpl;
+
/// \brief \c NAPTR class represents the NAPTR rdata defined in
/// RFC2915, RFC2168 and RFC3403
///
@@ -39,37 +43,24 @@ public:
// END_COMMON_MEMBERS
// NAPTR specific methods
+ ~NAPTR();
+
+ NAPTR& operator=(const NAPTR& source);
+
uint16_t getOrder() const;
uint16_t getPreference() const;
- const std::string& getFlags() const;
- const std::string& getServices() const;
- const std::string& getRegexp() const;
+ const std::string getFlags() const;
+ const std::string getServices() const;
+ const std::string getRegexp() const;
const Name& getReplacement() const;
private:
/// Helper template function for toWire()
///
/// \param outputer Where to write data in
template <typename T>
- void toWireHelper(T& outputer) const {
- outputer.writeUint16(order_);
- outputer.writeUint16(preference_);
-
- outputer.writeUint8(flags_.size());
- outputer.writeData(flags_.c_str(), flags_.size());
-
- outputer.writeUint8(services_.size());
- outputer.writeData(services_.c_str(), services_.size());
-
- outputer.writeUint8(regexp_.size());
- outputer.writeData(regexp_.c_str(), regexp_.size());
- }
+ void toWireHelper(T& outputer) const;
- uint16_t order_;
- uint16_t preference_;
- std::string flags_;
- std::string services_;
- std::string regexp_;
- Name replacement_;
+ boost::scoped_ptr<NAPTRImpl> impl_;
};
// END_RDATA_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/soa_6.cc b/src/lib/dns/rdata/generic/soa_6.cc
index 3ff2f08..56eb7a2 100644
--- a/src/lib/dns/rdata/generic/soa_6.cc
+++ b/src/lib/dns/rdata/generic/soa_6.cc
@@ -14,22 +14,29 @@
#include <config.h>
-#include <string>
-
-#include <boost/static_assert.hpp>
-#include <boost/lexical_cast.hpp>
-
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <dns/name.h>
+#include <dns/master_lexer.h>
+#include <dns/master_loader.h>
+#include <dns/master_loader_callbacks.h>
#include <dns/messagerenderer.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
+#include <dns/rdata/generic/detail/lexer_util.h>
+
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+#include <sstream>
+
using namespace std;
using boost::lexical_cast;
using namespace isc::util;
+using isc::dns::rdata::generic::detail::createNameFromLexer;
// BEGIN_ISC_NAMESPACE
// BEGIN_RDATA_NAMESPACE
@@ -42,35 +49,85 @@ SOA::SOA(InputBuffer& buffer, size_t) :
buffer.readData(numdata_, sizeof(numdata_));
}
+namespace {
+void
+fillParameters(MasterLexer& lexer, uint8_t numdata[20]) {
+ // Copy serial, refresh, retry, expire, minimum. We accept the extended
+ // TTL-compatible style for the latter four.
+ OutputBuffer buffer(20);
+ buffer.writeUint32(lexer.getNextToken(MasterToken::NUMBER).getNumber());
+ for (int i = 0; i < 4; ++i) {
+ buffer.writeUint32(RRTTL(lexer.getNextToken(MasterToken::STRING).
+ getString()).getValue());
+ }
+ memcpy(numdata, buffer.getData(), buffer.getLength());
+}
+}
+
+/// \brief Constructor from string.
+///
+/// The given string must represent a valid SOA RDATA. There can be extra
+/// space characters at the beginning or end of the text (which are simply
+/// ignored), but other extra text, including a new line, will make the
+/// construction fail with an exception.
+///
+/// The MNAME and RNAME must be absolute since there's no parameter that
+/// specifies the origin name; if these are not absolute, \c MissingNameOrigin
+/// exception will be thrown. These must not be represented as a quoted
+/// string.
+///
+/// See the construction that takes \c MasterLexer for other fields.
+///
+/// \throw Others Exception from the Name and RRTTL constructors.
+/// \throw InvalidRdataText Other general syntax errors.
SOA::SOA(const std::string& soastr) :
- mname_("."), rname_(".") // quick hack workaround
+ // Fill in dummy name and replace them soon below.
+ mname_(Name::ROOT_NAME()), rname_(Name::ROOT_NAME())
{
- istringstream iss(soastr);
- string token;
-
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA MNAME");
+ try {
+ std::istringstream ss(soastr);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ mname_ = createNameFromLexer(lexer, NULL);
+ rname_ = createNameFromLexer(lexer, NULL);
+ fillParameters(lexer, numdata_);
+
+ if (lexer.getNextToken().getType() != MasterToken::END_OF_FILE) {
+ isc_throw(InvalidRdataText, "extra input text for SOA: "
+ << soastr);
+ }
+ } catch (const MasterLexer::LexerError& ex) {
+ isc_throw(InvalidRdataText, "Failed to construct SOA from '" <<
+ soastr << "': " << ex.what());
}
- mname_ = Name(token);
- iss >> token;
- if (iss.bad() || iss.fail()) {
- isc_throw(InvalidRdataText, "Invalid SOA RNAME");
- }
- rname_ = Name(token);
+}
- uint32_t serial, refresh, retry, expire, minimum;
- iss >> serial >> refresh >> retry >> expire >> minimum;
- if (iss.rdstate() != ios::eofbit) {
- isc_throw(InvalidRdataText, "Invalid SOA format");
- }
- OutputBuffer buffer(20);
- buffer.writeUint32(serial);
- buffer.writeUint32(refresh);
- buffer.writeUint32(retry);
- buffer.writeUint32(expire);
- buffer.writeUint32(minimum);
- memcpy(numdata_, buffer.getData(), buffer.getLength());
+/// \brief Constructor with a context of MasterLexer.
+///
+/// The \c lexer should point to the beginning of valid textual representation
+/// of an SOA RDATA. The MNAME and RNAME fields can be non absolute if
+/// \c origin is non NULL, in which case \c origin is used to make them
+/// absolute. These must not be represented as a quoted string.
+///
+/// The REFRESH, RETRY, EXPIRE, and MINIMUM fields can be either a valid
+/// decimal representation of an unsigned 32-bit integer or other
+/// valid textual representation of \c RRTTL such as "1H" (which means 3600).
+///
+/// \throw MasterLexer::LexerError General parsing error such as missing field.
+/// \throw Other Exceptions from the Name and RRTTL constructors if
+/// construction of textual fields as these objects fail.
+///
+/// \param lexer A \c MasterLexer object parsing a master file for the
+/// RDATA to be created
+/// \param origin If non NULL, specifies the origin of MNAME and RNAME when
+/// they are non absolute.
+SOA::SOA(MasterLexer& lexer, const Name* origin,
+ MasterLoader::Options, MasterLoaderCallbacks&) :
+ mname_(createNameFromLexer(lexer, origin)),
+ rname_(createNameFromLexer(lexer, origin))
+{
+ fillParameters(lexer, numdata_);
}
SOA::SOA(const Name& mname, const Name& rname, uint32_t serial,
diff --git a/src/lib/dns/rrclass-placeholder.h b/src/lib/dns/rrclass-placeholder.h
index 70d6b78..1ff4163 100644
--- a/src/lib/dns/rrclass-placeholder.h
+++ b/src/lib/dns/rrclass-placeholder.h
@@ -22,6 +22,8 @@
#include <exceptions/exceptions.h>
+#include <boost/optional.hpp>
+
namespace isc {
namespace util {
class InputBuffer;
@@ -33,6 +35,16 @@ namespace dns {
// forward declarations
class AbstractMessageRenderer;
+class RRClass; // forward declaration to define MaybeRRClass.
+
+/// \brief A shortcut for a compound type to represent RRClass-or-not.
+///
+/// A value of this type can be interpreted in a boolean context, whose
+/// value is \c true if and only if it contains a valid RRClass object.
+/// And, if it contains a valid RRClass object, its value is accessible
+/// using \c operator*, just like a bare pointer to \c RRClass.
+typedef boost::optional<RRClass> MaybeRRClass;
+
///
/// \brief A standard DNS module exception that is thrown if an RRClass object
/// is being constructed from an unrecognized string.
@@ -123,8 +135,8 @@ public:
/// If the given string is not recognized as a valid representation of
/// an RR class, an exception of class \c InvalidRRClass will be thrown.
///
- /// \param classstr A string representation of the \c RRClass
- explicit RRClass(const std::string& classstr);
+ /// \param class_str A string representation of the \c RRClass
+ explicit RRClass(const std::string& class_str);
/// Constructor from wire-format data.
///
/// The \c buffer parameter normally stores a complete DNS message
@@ -136,6 +148,45 @@ public:
///
/// \param buffer A buffer storing the wire format data.
explicit RRClass(isc::util::InputBuffer& buffer);
+
+ /// A separate factory of RRClass from text.
+ ///
+ /// This static method is similar to the constructor that takes a
+ /// string object, but works as a factory and reports parsing
+ /// failure in the form of the return value. Normally the
+ /// constructor version should suffice, but in some cases the caller
+ /// may have to expect mixture of valid and invalid input, and may
+ /// want to minimize the overhead of possible exception handling.
+ /// This version is provided for such purpose.
+ ///
+ /// For the format of the \c class_str argument, see the
+ /// <code>RRClass(const std::string&)</code> constructor.
+ ///
+ /// If the given text represents a valid RRClass, it returns a
+ /// \c MaybeRRClass object that stores a corresponding \c RRClass
+ /// object, which is accessible via \c operator*(). In this case
+ /// the returned object will be interpreted as \c true in a boolean
+ /// context. If the given text does not represent a valid RRClass,
+ /// it returns a \c MaybeRRClass object which is interpreted as
+ /// \c false in a boolean context.
+ ///
+ /// One main purpose of this function is to minimize the overhead
+ /// when the given text does not represent a valid RR class. For
+ /// this reason this function intentionally omits the capability of
+ /// delivering a detailed reason for the parse failure, such as in the
+ /// \c want() string when exception is thrown from the constructor
+ /// (it will internally require a creation of string object, which
+ /// is relatively expensive). If such detailed information is
+ /// necessary, the constructor version should be used to catch the
+ /// resulting exception.
+ ///
+ /// This function never throws the \c InvalidRRClass exception.
+ ///
+ /// \param class_str A string representation of the \c RRClass.
+ /// \return A MaybeRRClass object either storing an RRClass object
+ /// for the given text or a \c false value.
+ static MaybeRRClass createFromText(const std::string& class_str);
+
///
/// We use the default copy constructor intentionally.
//@}
diff --git a/src/lib/dns/rrclass.cc b/src/lib/dns/rrclass.cc
index ac5823c..76a2e73 100644
--- a/src/lib/dns/rrclass.cc
+++ b/src/lib/dns/rrclass.cc
@@ -30,8 +30,11 @@ using namespace isc::util;
namespace isc {
namespace dns {
-RRClass::RRClass(const std::string& classstr) {
- classcode_ = RRParamRegistry::getRegistry().textToClassCode(classstr);
+RRClass::RRClass(const std::string& class_str) {
+ if (!RRParamRegistry::getRegistry().textToClassCode(class_str, classcode_)) {
+ isc_throw(InvalidRRClass,
+ "Unrecognized RR class string: " + class_str);
+ }
}
RRClass::RRClass(InputBuffer& buffer) {
@@ -56,6 +59,16 @@ RRClass::toWire(AbstractMessageRenderer& renderer) const {
renderer.writeUint16(classcode_);
}
+MaybeRRClass
+RRClass::createFromText(const string& class_str) {
+ uint16_t class_code;
+ if (RRParamRegistry::getRegistry().textToClassCode(class_str,
+ class_code)) {
+ return (MaybeRRClass(class_code));
+ }
+ return (MaybeRRClass());
+}
+
ostream&
operator<<(ostream& os, const RRClass& rrclass) {
os << rrclass.toText();
diff --git a/src/lib/dns/rrcollator.cc b/src/lib/dns/rrcollator.cc
index 4b12222..153de04 100644
--- a/src/lib/dns/rrcollator.cc
+++ b/src/lib/dns/rrcollator.cc
@@ -42,7 +42,7 @@ public:
const RdataPtr& rdata);
RRsetPtr current_rrset_;
- AddRRsetCallback callback_;
+ const AddRRsetCallback callback_;
};
namespace {
diff --git a/src/lib/dns/rrparamregistry-placeholder.cc b/src/lib/dns/rrparamregistry-placeholder.cc
index 16ec23c..5960759 100644
--- a/src/lib/dns/rrparamregistry-placeholder.cc
+++ b/src/lib/dns/rrparamregistry-placeholder.cc
@@ -421,14 +421,15 @@ removeParam(uint16_t code, MC& codemap, MS& stringmap) {
return (false);
}
-template <typename PT, typename MS, typename ET>
-inline uint16_t
-textToCode(const string& code_str, MS& stringmap) {
+template <typename PT, typename MS>
+inline bool
+textToCode(const string& code_str, MS& stringmap, uint16_t& ret_code) {
typename MS::const_iterator found;
found = stringmap.find(code_str);
if (found != stringmap.end()) {
- return (found->second->code_);
+ ret_code = found->second->code_;
+ return (true);
}
size_t l = code_str.size();
@@ -441,10 +442,12 @@ textToCode(const string& code_str, MS& stringmap) {
l - PT::UNKNOWN_PREFIXLEN()));
iss >> dec >> code;
if (iss.rdstate() == ios::eofbit && code <= PT::MAX_CODE) {
- return (code);
+ ret_code = code;
+ return (true);
}
}
- isc_throw(ET, "Unrecognized RR parameter string: " + code_str);
+
+ return (false);
}
template <typename PT, typename MC>
@@ -475,10 +478,12 @@ RRParamRegistry::removeType(uint16_t code) {
impl_->str2typemap));
}
-uint16_t
-RRParamRegistry::textToTypeCode(const string& type_string) const {
- return (textToCode<RRTypeParam, StrRRTypeMap,
- InvalidRRType>(type_string, impl_->str2typemap));
+bool
+RRParamRegistry::textToTypeCode(const string& type_string,
+ uint16_t& type_code) const
+{
+ return (textToCode<RRTypeParam, StrRRTypeMap>
+ (type_string, impl_->str2typemap, type_code));
}
string
@@ -499,10 +504,12 @@ RRParamRegistry::removeClass(uint16_t code) {
impl_->str2classmap));
}
-uint16_t
-RRParamRegistry::textToClassCode(const string& class_string) const {
- return (textToCode<RRClassParam, StrRRClassMap,
- InvalidRRClass>(class_string, impl_->str2classmap));
+bool
+RRParamRegistry::textToClassCode(const string& class_string,
+ uint16_t& class_code) const
+{
+ return (textToCode<RRClassParam, StrRRClassMap>
+ (class_string, impl_->str2classmap, class_code));
}
string
diff --git a/src/lib/dns/rrparamregistry.h b/src/lib/dns/rrparamregistry.h
index 56ae981..bf86436 100644
--- a/src/lib/dns/rrparamregistry.h
+++ b/src/lib/dns/rrparamregistry.h
@@ -384,16 +384,22 @@ public:
/// \brief Convert a textual representation of an RR type to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR type to the corresponding integer
- /// code. If a mapping is found, it returns the associated type code;
- /// otherwise, if the given string is in the form of "TYPEnnnn", it returns
- /// the corresponding number as the type code; otherwise, it throws an
- /// exception of class \c InvalidRRType.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR type to the corresponding
+ /// integer code. If a mapping is found, it returns true with the
+ /// associated type code in \c type_code; otherwise, if the given
+ /// string is in the form of "TYPEnnnn", it returns true with the
+ /// corresponding number as the type code in \c type_code;
+ /// otherwise, it returns false and \c type_code is untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param type_string The textual representation of the RR type.
- /// \return The RR type code for \c type_string.
- uint16_t textToTypeCode(const std::string& type_string) const;
+ /// \param type_code Returns the RR type code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToTypeCode(const std::string& type_string,
+ uint16_t& type_code) const;
/// \brief Convert type code into its textual representation.
///
@@ -415,16 +421,23 @@ public:
/// \brief Convert a textual representation of an RR class to the
/// corresponding 16-bit integer code.
///
- /// This method searches the \c RRParamRegistry for the mapping from the
- /// given textual representation of RR class to the corresponding integer
- /// code. If a mapping is found, it returns the associated class code;
- /// otherwise, if the given string is in the form of "CLASSnnnn", it returns
- /// the corresponding number as the class code; otherwise, it throws an
- /// exception of class \c InvalidRRClass.
+ /// This method searches the \c RRParamRegistry for the mapping from
+ /// the given textual representation of RR class to the
+ /// corresponding integer code. If a mapping is found, it returns
+ /// true with the associated class code in \c class_code; otherwise,
+ /// if the given string is in the form of "CLASSnnnn", it returns
+ /// true with the corresponding number as the class code in
+ /// \c class_code; otherwise, it returns false and \c class_code is
+ /// untouched.
+ ///
+ /// It returns \c false and avoids throwing an exception in the case
+ /// of an error to avoid the exception overhead in some situations.
///
/// \param class_string The textual representation of the RR class.
- /// \return The RR class code for \c class_string.
- uint16_t textToClassCode(const std::string& class_string) const;
+ /// \param class_code Returns the RR class code in this argument.
+ /// \return true if conversion is successful, false otherwise.
+ bool textToClassCode(const std::string& class_string,
+ uint16_t& class_code) const;
/// \brief Convert class code into its textual representation.
///
diff --git a/src/lib/dns/rrset_collection.cc b/src/lib/dns/rrset_collection.cc
new file mode 100644
index 0000000..8711c3f
--- /dev/null
+++ b/src/lib/dns/rrset_collection.cc
@@ -0,0 +1,128 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/master_loader_callbacks.h>
+#include <dns/master_loader.h>
+#include <dns/rrcollator.h>
+
+#include <exceptions/exceptions.h>
+
+#include <boost/bind.hpp>
+
+using namespace isc;
+
+namespace isc {
+namespace dns {
+
+void
+RRsetCollection::loaderCallback(const std::string&, size_t, const std::string&)
+{
+ // We just ignore callbacks for errors and warnings.
+}
+
+void
+RRsetCollection::addRRset(RRsetPtr rrset) {
+ const CollectionKey key(rrset->getClass(), rrset->getType(),
+ rrset->getName());
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ isc_throw(InvalidParameter,
+ "RRset for " << rrset->getName() << "/" << rrset->getClass()
+ << " with type " << rrset->getType() << " already exists");
+ }
+
+ rrsets_.insert(std::pair<CollectionKey, RRsetPtr>(key, rrset));
+}
+
+template<typename T>
+void
+RRsetCollection::constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass)
+{
+ RRCollator collator(boost::bind(&RRsetCollection::addRRset, this, _1));
+ MasterLoaderCallbacks callbacks
+ (boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3),
+ boost::bind(&RRsetCollection::loaderCallback, this, _1, _2, _3));
+ MasterLoader loader(source, origin, rrclass, callbacks,
+ collator.getCallback(),
+ MasterLoader::DEFAULT);
+ loader.load();
+ collator.flush();
+}
+
+RRsetCollection::RRsetCollection(const char* filename, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper(filename, origin, rrclass);
+}
+
+RRsetCollection::RRsetCollection(std::istream& input_stream, const Name& origin,
+ const RRClass& rrclass)
+{
+ constructHelper<std::istream&>(input_stream, origin, rrclass);
+}
+
+RRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) {
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (RRsetPtr());
+}
+
+ConstRRsetPtr
+RRsetCollection::find(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype) const
+{
+ const CollectionKey key(rrclass, rrtype, name);
+ CollectionMap::const_iterator it = rrsets_.find(key);
+ if (it != rrsets_.end()) {
+ return (it->second);
+ }
+ return (ConstRRsetPtr());
+}
+
+bool
+RRsetCollection::removeRRset(const Name& name, const RRClass& rrclass,
+ const RRType& rrtype)
+{
+ const CollectionKey key(rrclass, rrtype, name);
+
+ CollectionMap::iterator it = rrsets_.find(key);
+ if (it == rrsets_.end()) {
+ return (false);
+ }
+
+ rrsets_.erase(it);
+ return (true);
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getBeginning() {
+ CollectionMap::iterator it = rrsets_.begin();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+RRsetCollectionBase::IterPtr
+RRsetCollection::getEnd() {
+ CollectionMap::iterator it = rrsets_.end();
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+}
+
+} // end of namespace dns
+} // end of namespace isc
diff --git a/src/lib/dns/rrset_collection.h b/src/lib/dns/rrset_collection.h
new file mode 100644
index 0000000..62dd9a9
--- /dev/null
+++ b/src/lib/dns/rrset_collection.h
@@ -0,0 +1,172 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_H
+#define RRSET_COLLECTION_H 1
+
+#include <dns/rrset_collection_base.h>
+#include <dns/rrclass.h>
+
+#include <boost/tuple/tuple.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <map>
+
+namespace isc {
+namespace dns {
+
+/// \brief libdns++ implementation of RRsetCollectionBase using an STL
+/// container.
+class RRsetCollection : public RRsetCollectionBase {
+public:
+ /// \brief Constructor.
+ ///
+ /// This constructor creates an empty collection without any data in
+ /// it. RRsets can be added to the collection with the \c addRRset()
+ /// method.
+ RRsetCollection() {}
+
+ /// \brief Constructor.
+ ///
+ /// The \c origin and \c rrclass arguments are required for the zone
+ /// loading, but \c RRsetCollection itself does not do any
+ /// validation, and the collection of RRsets does not have to form a
+ /// valid zone.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param filename Name of a file containing a collection of RRs in
+ /// the master file format (which may or may not form a valid zone).
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(const char* filename, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Constructor.
+ ///
+ /// This constructor is similar to the previous one, but instead of
+ /// taking a filename to load a zone from, it takes an input
+ /// stream.
+ ///
+ /// \throws MasterLoaderError if there is an error during loading.
+ /// \param input_stream The input stream to load from.
+ /// \param origin The zone origin.
+ /// \param rrclass The zone class.
+ RRsetCollection(std::istream& input_stream, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+
+ /// \brief Destructor
+ virtual ~RRsetCollection() {}
+
+ /// \brief Add an RRset to the collection.
+ ///
+ /// Does not do any validation whether \c rrset belongs to a
+ /// particular zone or not. A reference to \c rrset is taken in an
+ /// internally managed \c shared_ptr, so even if the caller's
+ /// \c RRsetPtr is destroyed, the RRset it wrapped is still alive
+ /// and managed by the \c RRsetCollection. It throws an
+ /// \c isc::InvalidParameter exception if an rrset with the same
+ /// class, type and name already exists.
+ ///
+ /// Callers must not modify the RRset after adding it to the
+ /// collection, as the rrset is indexed internally by the
+ /// collection.
+ void addRRset(isc::dns::RRsetPtr rrset);
+
+ /// \brief Remove an RRset from the collection.
+ ///
+ /// RRset(s) matching the \c name, \c rrclass and \c rrtype are
+ /// removed from the collection.
+ ///
+ /// \return \c true if a matching RRset was deleted, \c false if no
+ /// such RRset exists.
+ bool removeRRset(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// \param name The name of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype) const;
+
+ /// \brief Find a matching RRset in the collection (non-const
+ /// variant).
+ ///
+ /// See above for a description of the method and arguments.
+ isc::dns::RRsetPtr find(const isc::dns::Name& name,
+ const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype);
+
+private:
+ template<typename T>
+ void constructHelper(T source, const isc::dns::Name& origin,
+ const isc::dns::RRClass& rrclass);
+ void loaderCallback(const std::string&, size_t, const std::string&);
+
+ typedef boost::tuple<isc::dns::RRClass, isc::dns::RRType, isc::dns::Name>
+ CollectionKey;
+ typedef std::map<CollectionKey, isc::dns::RRsetPtr> CollectionMap;
+
+ CollectionMap rrsets_;
+
+protected:
+ class DnsIter : public RRsetCollectionBase::Iter {
+ public:
+ DnsIter(CollectionMap::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ isc::dns::RRsetPtr& rrset = iter_->second;
+ return (*rrset);
+ }
+
+ virtual IterPtr getNext() {
+ CollectionMap::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new DnsIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const DnsIter* other_real = dynamic_cast<DnsIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ CollectionMap::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning();
+ virtual RRsetCollectionBase::IterPtr getEnd();
+};
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrset_collection_base.h b/src/lib/dns/rrset_collection_base.h
new file mode 100644
index 0000000..7ccf7b5
--- /dev/null
+++ b/src/lib/dns/rrset_collection_base.h
@@ -0,0 +1,197 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef RRSET_COLLECTION_BASE_H
+#define RRSET_COLLECTION_BASE_H 1
+
+#include <dns/rrset.h>
+#include <dns/name.h>
+
+#include <boost/shared_ptr.hpp>
+
+#include <iterator>
+
+namespace isc {
+namespace dns {
+
+/// \brief Error during RRsetCollectionBase find() operation
+///
+/// This exception is thrown when an calling implementation of
+/// \c RRsetCollectionBase::find() results in an error which is not due
+/// to unmatched data, but because of some other underlying error
+/// condition.
+class RRsetCollectionError : public Exception {
+public:
+ RRsetCollectionError(const char* file, size_t line, const char* what) :
+ Exception(file, line, what)
+ {}
+};
+
+/// \brief Generic class to represent a set of RRsets.
+///
+/// This is a generic container and the stored set of RRsets does not
+/// necessarily form a valid zone (e.g. there doesn't necessarily have
+/// to be an SOA at the "origin"). Instead, it will be used to represent
+/// a single zone for the purpose of zone loading/checking. It provides
+/// a simple find() method to find an RRset for the given name and type
+/// (and maybe class) and a way to iterate over all RRsets.
+///
+/// See \c RRsetCollection for a simple libdns++ implementation using an
+/// STL container. libdatasrc will have another implementation.
+class RRsetCollectionBase {
+public:
+ /// \brief Find a matching RRset in the collection.
+ ///
+ /// Returns the RRset in the collection that exactly matches the
+ /// given \c name, \c rrclass and \c rrtype. If no matching RRset
+ /// is found, \c NULL is returned.
+ ///
+ /// This method's implementations currently are not specified to
+ /// handle \c RRTypes such as RRSIG and NSEC3. RRSIGs are attached
+ /// to their corresponding \c RRset and it is not straightforward to
+ /// search for them. Searching for RRSIGs will return \c false
+ /// always. Support for RRSIGs may be added in the future.
+ ///
+ /// Non-concrete types such as ANY and AXFR are unsupported and will
+ /// return \c false always.
+ ///
+ /// \throw RRsetCollectionError if find() results in some
+ /// implementation-specific error.
+ /// \param name The name of the RRset to search for.
+ /// \param rrtype The type of the RRset to search for.
+ /// \param rrclass The class of the RRset to search for.
+ /// \return The RRset if found, \c NULL otherwise.
+ virtual isc::dns::ConstRRsetPtr find
+ (const isc::dns::Name& name, const isc::dns::RRClass& rrclass,
+ const isc::dns::RRType& rrtype)
+ const = 0;
+
+ /// \brief Destructor
+ virtual ~RRsetCollectionBase() {}
+
+protected:
+ class Iter; // forward declaration
+
+ /// \brief Wraps Iter with a reference count.
+ typedef boost::shared_ptr<Iter> IterPtr;
+
+ /// \brief A helper iterator interface for \c RRsetCollectionBase.
+ ///
+ /// This is a protected iterator class that is a helper interface
+ /// used by the public iterator. Derived classes of
+ /// \c RRsetCollectionBase are supposed to implement this class and
+ /// the \c getBeginning() and \c getEnd() methods, so that the
+ /// public interator interface can be provided. This is a forward
+ /// iterator only.
+ class Iter {
+ public:
+ virtual ~Iter() {};
+
+ /// \brief Returns the \c AbstractRRset currently pointed to by
+ /// the iterator.
+ virtual const isc::dns::AbstractRRset& getValue() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to
+ /// the next \c AbstractRRset in sequence in the collection.
+ virtual IterPtr getNext() = 0;
+
+ /// \brief Check if another iterator is equal to this one.
+ ///
+ /// Returns \c true if this iterator is equal to \c other,
+ /// \c false otherwise. Note that if \c other is not the same
+ /// type as \c this, or cannot be compared meaningfully, the
+ /// method must return \c false.
+ ///
+ /// \param other The other iterator to compare against.
+ /// \return \c true if equal, \c false otherwise.
+ virtual bool equals(Iter& other) = 0;
+ };
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing to the
+ /// beginning of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getBeginning() = 0;
+
+ /// \brief Returns an \c IterPtr wrapping an Iter pointing past the
+ /// end of the collection.
+ ///
+ /// \throw isc::dns::RRsetCollectionError if using the iterator
+ /// results in some underlying datasrc error.
+ virtual IterPtr getEnd() = 0;
+
+public:
+ /// \brief A forward \c std::iterator for \c RRsetCollectionBase.
+ ///
+ /// It behaves like a \c std::iterator forward iterator, so please
+ /// see its documentation for usage.
+ class Iterator : std::iterator<std::forward_iterator_tag,
+ const isc::dns::AbstractRRset>
+ {
+ public:
+ explicit Iterator(IterPtr iter) :
+ iter_(iter)
+ {}
+
+ reference operator*() {
+ return (iter_->getValue());
+ }
+
+ Iterator& operator++() {
+ iter_ = iter_->getNext();
+ return (*this);
+ }
+
+ Iterator operator++(int) {
+ Iterator tmp(iter_);
+ ++*this;
+ return (tmp);
+ }
+
+ bool operator==(const Iterator& other) const {
+ return (iter_->equals(*other.iter_));
+ }
+
+ bool operator!=(const Iterator& other) const {
+ return (!iter_->equals(*other.iter_));
+ }
+
+ private:
+ IterPtr iter_;
+ };
+
+ /// \brief Returns an iterator pointing to the beginning of the
+ /// collection.
+ Iterator begin() {
+ return Iterator(getBeginning());
+ }
+
+ /// \brief Returns an iterator pointing past the end of the
+ /// collection.
+ Iterator end() {
+ return Iterator(getEnd());
+ }
+};
+
+typedef boost::shared_ptr<RRsetCollectionBase> RRsetCollectionPtr;
+
+} // end of namespace dns
+} // end of namespace isc
+
+#endif // RRSET_COLLECTION_BASE_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index 7904d84..23d57f4 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -133,7 +133,7 @@ public:
/// One main purpose of this function is to minimize the overhead
/// when the given text does not represent a valid RR TTL. For this
/// reason this function intentionally omits the capability of delivering
- /// details reason for the parse failure, such as in the \c want()
+ /// a detailed reason for the parse failure, such as in the \c want()
/// string when exception is thrown from the constructor (it will
/// internally require a creation of string object, which is relatively
/// expensive). If such detailed information is necessary, the constructor
diff --git a/src/lib/dns/rrtype.cc b/src/lib/dns/rrtype.cc
index 4ef4e67..84892bf 100644
--- a/src/lib/dns/rrtype.cc
+++ b/src/lib/dns/rrtype.cc
@@ -31,8 +31,11 @@ using isc::dns::RRType;
namespace isc {
namespace dns {
-RRType::RRType(const std::string& typestr) {
- typecode_ = RRParamRegistry::getRegistry().textToTypeCode(typestr);
+RRType::RRType(const std::string& type_str) {
+ if (!RRParamRegistry::getRegistry().textToTypeCode(type_str, typecode_)) {
+ isc_throw(InvalidRRType,
+ "Unrecognized RR type string: " + type_str);
+ }
}
RRType::RRType(InputBuffer& buffer) {
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index df42720..5029681 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -73,8 +73,9 @@ run_unittests_SOURCES += tsig_unittest.cc
run_unittests_SOURCES += tsigerror_unittest.cc
run_unittests_SOURCES += tsigkey_unittest.cc
run_unittests_SOURCES += tsigrecord_unittest.cc
-run_unittests_SOURCES += character_string_unittest.cc
run_unittests_SOURCES += master_loader_callbacks_test.cc
+run_unittests_SOURCES += rrset_collection_unittest.cc
+run_unittests_SOURCES += zone_checker_unittest.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
# We shouldn't need to include BOTAN_LIBS here, but there
diff --git a/src/lib/dns/tests/character_string_unittest.cc b/src/lib/dns/tests/character_string_unittest.cc
deleted file mode 100644
index e8f5884..0000000
--- a/src/lib/dns/tests/character_string_unittest.cc
+++ /dev/null
@@ -1,97 +0,0 @@
-// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
-//
-// Permission to use, copy, modify, and/or distribute this software for any
-// purpose with or without fee is hereby granted, provided that the above
-// copyright notice and this permission notice appear in all copies.
-//
-// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
-// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
-// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
-// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
-// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
-// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-// PERFORMANCE OF THIS SOFTWARE.
-
-
-#include <gtest/gtest.h>
-
-#include <dns/rdata.h>
-#include <dns/tests/unittest_util.h>
-#include <dns/character_string.h>
-
-using isc::UnitTestUtil;
-
-using namespace std;
-using namespace isc;
-using namespace isc::dns;
-using namespace isc::dns::characterstr;
-using namespace isc::dns::rdata;
-
-namespace {
-
-class CharacterString {
-public:
- CharacterString(const string& str){
- string::const_iterator it = str.begin();
- characterStr_ = getNextCharacterString(str, it, &is_quoted_);
- }
- const string& str() const { return characterStr_; }
- bool quoted() const { return (is_quoted_); }
-private:
- string characterStr_;
- bool is_quoted_;
-};
-
-TEST(CharacterStringTest, testNormalCase) {
- CharacterString cstr1("foo");
- EXPECT_EQ(string("foo"), cstr1.str());
-
- // Test <character-string> that separated by space
- CharacterString cstr2("foo bar");
- EXPECT_EQ(string("foo"), cstr2.str());
- EXPECT_FALSE(cstr2.quoted());
-
- // Test <character-string> that separated by quotes
- CharacterString cstr3("\"foo bar\"");
- EXPECT_EQ(string("foo bar"), cstr3.str());
- EXPECT_TRUE(cstr3.quoted());
-
- // Test <character-string> that not separate by quotes but ended with quotes
- CharacterString cstr4("foo\"");
- EXPECT_EQ(string("foo\""), cstr4.str());
- EXPECT_FALSE(cstr4.quoted());
-}
-
-TEST(CharacterStringTest, testBadCase) {
- // The <character-string> that started with quotes should also be ended
- // with quotes
- EXPECT_THROW(CharacterString cstr("\"foo"), InvalidRdataText);
-
- // The string length cannot exceed 255 characters
- string str;
- for (int i = 0; i < 257; ++i) {
- str += 'A';
- }
- EXPECT_THROW(CharacterString cstr(str), CharStringTooLong);
-}
-
-TEST(CharacterStringTest, testEscapeCharacter) {
- CharacterString cstr1("foo\\bar");
- EXPECT_EQ(string("foobar"), cstr1.str());
-
- CharacterString cstr2("foo\\\\bar");
- EXPECT_EQ(string("foo\\bar"), cstr2.str());
-
- CharacterString cstr3("fo\\111bar");
- EXPECT_EQ(string("foobar"), cstr3.str());
-
- CharacterString cstr4("fo\\1112bar");
- EXPECT_EQ(string("foo2bar"), cstr4.str());
-
- // There must be at least 3 digits followed by '\'
- EXPECT_THROW(CharacterString cstr("foo\\98ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\9ar"), InvalidRdataText);
- EXPECT_THROW(CharacterString cstr("foo\\98"), InvalidRdataText);
-}
-
-} // namespace
diff --git a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
index db0cbec..a26b02c 100644
--- a/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_inputsource_unittest.cc
@@ -13,6 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dns/master_lexer_inputsource.h>
+#include <dns/master_lexer.h>
#include <exceptions/exceptions.h>
#include <gtest/gtest.h>
@@ -29,10 +30,13 @@ using namespace isc::dns::master_lexer_internal;
namespace {
+const char* const test_input =
+ "Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n";
+
class InputSourceTest : public ::testing::Test {
protected:
InputSourceTest() :
- str_("Line1 to scan.\nLine2 to scan.\nLine3 to scan.\n"),
+ str_(test_input),
str_length_(strlen(str_)),
iss_(str_),
source_(iss_)
@@ -73,6 +77,7 @@ checkGetAndUngetChar(InputSource& source,
{
for (size_t i = 0; i < str_length; ++i) {
EXPECT_EQ(str[i], source.getChar());
+ EXPECT_EQ(i + 1, source.getPosition());
EXPECT_FALSE(source.atEOF());
}
@@ -85,6 +90,10 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be set.
EXPECT_TRUE(source.atEOF());
+ // It doesn't increase the position count.
+ EXPECT_EQ(str_length, source.getPosition());
+ EXPECT_EQ(str_length, source.getSize()); // this should be == getSize().
+
// Now, let's go backwards. This should cause the EOF to be set to
// false.
source.ungetChar();
@@ -92,6 +101,9 @@ checkGetAndUngetChar(InputSource& source,
// Now, EOF should be false.
EXPECT_FALSE(source.atEOF());
+ // But the position shouldn't change.
+ EXPECT_EQ(str_length, source.getPosition());
+
// This should cause EOF to be set again.
EXPECT_EQ(InputSource::END_OF_STREAM, source.getChar());
@@ -106,6 +118,7 @@ checkGetAndUngetChar(InputSource& source,
// Skip one character.
source.ungetChar();
EXPECT_EQ(str[index], source.getChar());
+ EXPECT_EQ(index + 1, source.getPosition());
// Skip the character we received again.
source.ungetChar();
}
@@ -144,6 +157,7 @@ TEST_F(InputSourceTest, ungetAll) {
// Now we are back to where we started.
EXPECT_EQ(1, source_.getCurrentLine());
EXPECT_FALSE(source_.atEOF());
+ EXPECT_EQ(0, source_.getPosition());
}
TEST_F(InputSourceTest, compact) {
@@ -175,6 +189,9 @@ TEST_F(InputSourceTest, compact) {
EXPECT_TRUE(source_.atEOF());
EXPECT_EQ(4, source_.getCurrentLine());
+ // compact shouldn't change the position count.
+ EXPECT_EQ(source_.getSize(), source_.getPosition());
+
// Skip the EOF.
source_.ungetChar();
@@ -322,4 +339,36 @@ TEST_F(InputSourceTest, saveLine) {
EXPECT_FALSE(source_.atEOF());
}
+TEST_F(InputSourceTest, getSize) {
+ // A simple case using string stream
+ EXPECT_EQ(strlen(test_input), source_.getSize());
+
+ // Check it works with an empty input
+ istringstream iss("");
+ EXPECT_EQ(0, InputSource(iss).getSize());
+
+ // Pretend there's an error in seeking in the stream. It will be
+ // considered a seek specific error, and getSize() returns "unknown".
+ iss.setstate(std::ios_base::failbit);
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, InputSource(iss).getSize());
+ // The fail bit should have been cleared.
+ EXPECT_FALSE(iss.fail());
+
+ // Pretend there's a *critical* error in the stream. The constructor will
+ // throw in the attempt of getting the input size.
+ iss.setstate(std::ios_base::badbit);
+ EXPECT_THROW(InputSource isrc(iss), InputSource::OpenError);
+
+ // Check with input source from file name. We hardcode the file size
+ // for simplicity. It won't change too often.
+ EXPECT_EQ(143, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getSize());
+}
+
+TEST_F(InputSourceTest, getPosition) {
+ // Initially the position is set to 0. Other cases are tested in tests
+ // for get and unget.
+ EXPECT_EQ(0, source_.getPosition());
+ EXPECT_EQ(0, InputSource(TEST_DATA_SRCDIR "/masterload.txt").getPosition());
+}
+
} // end namespace
diff --git a/src/lib/dns/tests/master_lexer_unittest.cc b/src/lib/dns/tests/master_lexer_unittest.cc
index 62ad79d..039a0c3 100644
--- a/src/lib/dns/tests/master_lexer_unittest.cc
+++ b/src/lib/dns/tests/master_lexer_unittest.cc
@@ -52,6 +52,7 @@ void
checkEmptySource(const MasterLexer& lexer) {
EXPECT_TRUE(lexer.getSourceName().empty());
EXPECT_EQ(0, lexer.getSourceLine());
+ EXPECT_EQ(0, lexer.getPosition());
}
TEST_F(MasterLexerTest, preOpen) {
@@ -61,9 +62,11 @@ TEST_F(MasterLexerTest, preOpen) {
TEST_F(MasterLexerTest, pushStream) {
EXPECT_EQ(0, lexer.getSourceCount());
+ ss << "test";
lexer.pushSource(ss);
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
EXPECT_EQ(1, lexer.getSourceCount());
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // 4 = len("test")
// From the point of view of this test, we only have to check (though
// indirectly) getSourceLine calls InputSource::getCurrentLine. It should
@@ -74,6 +77,16 @@ TEST_F(MasterLexerTest, pushStream) {
lexer.popSource();
EXPECT_EQ(0, lexer.getSourceCount());
checkEmptySource(lexer);
+ EXPECT_EQ(4, lexer.getTotalSourceSize()); // this shouldn't change
+}
+
+TEST_F(MasterLexerTest, pushStreamFail) {
+ // Pretend a "bad" thing happened in the stream. This will make the
+ // initialization throw an exception.
+ ss << "test";
+ ss.setstate(std::ios_base::badbit);
+
+ EXPECT_THROW(lexer.pushSource(ss), isc::Unexpected);
}
TEST_F(MasterLexerTest, pushFile) {
@@ -85,9 +98,14 @@ TEST_F(MasterLexerTest, pushFile) {
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
EXPECT_EQ(1, lexer.getSourceLine());
+ // 143 = size of the test zone file. hardcode it assuming it won't change
+ // too often.
+ EXPECT_EQ(143, lexer.getTotalSourceSize());
+
lexer.popSource();
checkEmptySource(lexer);
EXPECT_EQ(0, lexer.getSourceCount());
+ EXPECT_EQ(143, lexer.getTotalSourceSize()); // this shouldn't change
// If we give a non NULL string pointer, its content will be intact
// if pushSource succeeds.
@@ -116,19 +134,62 @@ TEST_F(MasterLexerTest, pushFileFail) {
}
TEST_F(MasterLexerTest, nestedPush) {
+ const string test_txt = "test";
+ ss << test_txt;
lexer.pushSource(ss);
+
+ EXPECT_EQ(test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(0, lexer.getPosition());
+
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ // Read the string; getPosition() should reflect that.
+ EXPECT_EQ(MasterToken::STRING, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size(), lexer.getPosition());
+
// We can push another source without popping the previous one.
lexer.pushSource(TEST_DATA_SRCDIR "/masterload.txt");
EXPECT_EQ(TEST_DATA_SRCDIR "/masterload.txt", lexer.getSourceName());
+ EXPECT_EQ(143 + test_txt.size(),
+ lexer.getTotalSourceSize()); // see above for magic nums
+
+ // the next token should be the EOL (skipping a comment line), its
+ // position in the file is 35 (hardcoded).
+ EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
// popSource() works on the "topmost" (last-pushed) source
lexer.popSource();
EXPECT_EQ(expected_stream_name, lexer.getSourceName());
+ // pop shouldn't change the total size and the current position
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+
lexer.popSource();
EXPECT_TRUE(lexer.getSourceName().empty());
+
+ // size and position still shouldn't change
+ EXPECT_EQ(143 + test_txt.size(), lexer.getTotalSourceSize());
+ EXPECT_EQ(test_txt.size() + 35, lexer.getPosition());
+}
+
+TEST_F(MasterLexerTest, unknownSourceSize) {
+ // Similar to the previous case, but the size of the second source
+ // will be considered "unknown" (by emulating an error).
+ ss << "test";
+ lexer.pushSource(ss);
+ EXPECT_EQ(4, lexer.getTotalSourceSize());
+
+ stringstream ss2;
+ ss2.setstate(std::ios_base::failbit); // this will make the size unknown
+ lexer.pushSource(ss2);
+ // Then the total size is also unknown.
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
+
+ // Even if we pop that source, the size is still unknown.
+ lexer.popSource();
+ EXPECT_EQ(MasterLexer::SOURCE_SIZE_UNKNOWN, lexer.getTotalSourceSize());
}
TEST_F(MasterLexerTest, invalidPop) {
@@ -141,25 +202,31 @@ TEST_F(MasterLexerTest, noSource) {
EXPECT_THROW(lexer.getNextToken(), isc::InvalidOperation);
}
-// Test getting some tokens
+// Test getting some tokens. It also check basic behavior of getPosition().
TEST_F(MasterLexerTest, getNextToken) {
ss << "\n \n\"STRING\"\n";
lexer.pushSource(ss);
// First, the newline should get out.
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Then the whitespace, if we specify the option.
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition());
// The newline
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5, lexer.getPosition()); // 1st \n + 3 spaces, then 2nd \n
// The (quoted) string
EXPECT_EQ(MasterToken::QSTRING,
lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(5 + 8, lexer.getPosition()); // 8 = len("STRING') + quotes
// And the end of line and file
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // previous + 3rd \n
EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
+ EXPECT_EQ(5 + 8 + 1, lexer.getPosition()); // position doesn't change
}
// Test we correctly find end of file.
@@ -204,20 +271,25 @@ TEST_F(MasterLexerTest, getUnbalancedString) {
EXPECT_EQ(MasterToken::END_OF_FILE, lexer.getNextToken().getType());
}
-// Test ungetting tokens works
+// Test ungetting tokens works. Also check getPosition() is adjusted
TEST_F(MasterLexerTest, ungetToken) {
ss << "\n (\"string\"\n) more";
lexer.pushSource(ss);
// Try getting the newline
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Return it and get again
lexer.ungetToken();
+ EXPECT_EQ(0, lexer.getPosition());
EXPECT_EQ(MasterToken::END_OF_LINE, lexer.getNextToken().getType());
+ EXPECT_EQ(1, lexer.getPosition());
// Get the string and return it back
EXPECT_EQ(MasterToken::QSTRING,
lexer.getNextToken(MasterLexer::QSTRING).getType());
+ EXPECT_EQ(string("\n (\"string\"").size(), lexer.getPosition());
lexer.ungetToken();
+ EXPECT_EQ(1, lexer.getPosition()); // back to just after 1st \n
// But if we change the options, it honors them
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::QSTRING |
@@ -253,7 +325,8 @@ TEST_F(MasterLexerTest, ungetRealOptions) {
}
// Check the initial whitespace is found even in the first line of included
-// file
+// file. It also confirms getPosition() works for multiple sources, each
+// of which is partially parsed.
TEST_F(MasterLexerTest, includeAndInitialWS) {
ss << " \n";
lexer.pushSource(ss);
@@ -263,9 +336,11 @@ TEST_F(MasterLexerTest, includeAndInitialWS) {
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(1, lexer.getPosition());
lexer.pushSource(ss2);
EXPECT_EQ(MasterToken::INITIAL_WS,
lexer.getNextToken(MasterLexer::INITIAL_WS).getType());
+ EXPECT_EQ(2, lexer.getPosition()); // should be sum of pushed positions.
}
// Test only one token can be ungotten
diff --git a/src/lib/dns/tests/master_loader_unittest.cc b/src/lib/dns/tests/master_loader_unittest.cc
index 89a1440..636f73d 100644
--- a/src/lib/dns/tests/master_loader_unittest.cc
+++ b/src/lib/dns/tests/master_loader_unittest.cc
@@ -153,12 +153,23 @@ TEST_F(MasterLoaderTest, basicLoad) {
RRClass::IN(), MasterLoader::MANY_ERRORS);
EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // The following three should be set to 0 initially in case the loader
+ // is constructed from a file name.
+ EXPECT_EQ(0, loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
loader_->load();
EXPECT_TRUE(loader_->loadedSucessfully());
EXPECT_TRUE(errors_.empty());
EXPECT_TRUE(warnings_.empty());
+ // Hardcode expected values taken from the test data file, assuming it
+ // won't change too often.
+ EXPECT_EQ(549, loader_->getSize());
+ EXPECT_EQ(549, loader_->getPosition());
+
checkBasicRRs();
}
@@ -195,6 +206,47 @@ TEST_F(MasterLoaderTest, include) {
}
}
+TEST_F(MasterLoaderTest, includeAndIncremental) {
+ // Check getSize() and getPosition() are adjusted before and after
+ // $INCLUDE.
+ const string first_rr = "before.example.org. 0 A 192.0.2.1\n";
+ const string include_str = "$INCLUDE " TEST_DATA_SRCDIR "/example.org";
+ const string zone_data = first_rr + include_str + "\n" +
+ "www 3600 IN AAAA 2001:db8::1\n";
+ stringstream ss(zone_data);
+ setLoader(ss, Name("example.org."), RRClass::IN(), MasterLoader::DEFAULT);
+
+ // On construction, getSize() returns the size of the data (exclude the
+ // the file to be included); position is set to 0.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
+ // Read the first RR. getSize() doesn't change; position should be
+ // at the end of the first line.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(first_rr.size(), loader_->getPosition());
+
+ // Read next 4. It includes $INCLUDE processing. Magic number of 549
+ // is the size of the test zone file (see above); 506 is the position in
+ // the file at the end of 4th RR (due to extra comments it's smaller than
+ // the file size).
+ loader_->loadIncremental(4);
+ EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+ EXPECT_EQ(first_rr.size() + include_str.size() + 506,
+ loader_->getPosition());
+
+ // Read the last one. At this point getSize and getPosition return
+ // the same value, indicating progress of 100%.
+ loader_->loadIncremental(1);
+ EXPECT_EQ(zone_data.size() + 549, loader_->getSize());
+ EXPECT_EQ(zone_data.size() + 549, loader_->getPosition());
+
+ // we were not interested in checking RRs in this test. clear them to
+ // not confuse TearDown().
+ rrsets_.clear();
+}
+
// A commonly used helper to check callback message.
void
checkCallbackMessage(const string& actual_msg, const string& expected_msg,
@@ -260,7 +312,7 @@ 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
+ // We perform the test with MANY_ERRORS, we want to see what happens
// after the error.
setLoader(ss, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
@@ -277,11 +329,19 @@ TEST_F(MasterLoaderTest, popAfterError) {
// Check it works the same when created based on a stream, not filename
TEST_F(MasterLoaderTest, streamConstructor) {
- stringstream zone_stream(prepareZone("", true));
+ const string zone_data(prepareZone("", true));
+ stringstream zone_stream(zone_data);
setLoader(zone_stream, Name("example.org."), RRClass::IN(),
MasterLoader::MANY_ERRORS);
EXPECT_FALSE(loader_->loadedSucessfully());
+
+ // Unlike the basicLoad test, if we construct the loader from a stream
+ // getSize() returns the data size in the stream immediately after the
+ // construction.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(0, loader_->getPosition());
+
loader_->load();
EXPECT_TRUE(loader_->loadedSucessfully());
@@ -290,6 +350,11 @@ TEST_F(MasterLoaderTest, streamConstructor) {
checkRR("example.org", RRType::SOA(), "ns1.example.org. "
"admin.example.org. 1234 3600 1800 2419200 7200");
checkRR("correct.example.org", RRType::A(), "192.0.2.2");
+
+ // On completion of the load, both getSize() and getPosition() return the
+ // size of the data.
+ EXPECT_EQ(zone_data.size(), loader_->getSize());
+ EXPECT_EQ(zone_data.size(), loader_->getPosition());
}
// Try loading data incrementally.
@@ -391,7 +456,8 @@ struct ErrorCase {
"Missing Rdata" },
{ "www 3600 IN", NULL, "Unexpected EOLN" },
- { "www 3600 CH TXT nothing", NULL, "Class mismatch" },
+ { "www 3600 CH TXT nothing", "Class mismatch: CH vs. IN",
+ "Class mismatch" },
{ "www \"3600\" IN A 192.0.2.1", NULL, "Quoted TTL" },
{ "www 3600 \"IN\" A 192.0.2.1", NULL, "Quoted class" },
{ "www 3600 IN \"A\" 192.0.2.1", NULL, "Quoted type" },
diff --git a/src/lib/dns/tests/rdata_char_string_unittest.cc b/src/lib/dns/tests/rdata_char_string_unittest.cc
index 9d23622..3059669 100644
--- a/src/lib/dns/tests/rdata_char_string_unittest.cc
+++ b/src/lib/dns/tests/rdata_char_string_unittest.cc
@@ -14,8 +14,10 @@
#include <util/unittests/wiredata.h>
+#include <dns/exceptions.h>
#include <dns/rdata.h>
#include <dns/rdata/generic/detail/char_string.h>
+#include <util/buffer.h>
#include <gtest/gtest.h>
@@ -25,7 +27,10 @@
using namespace isc::dns;
using namespace isc::dns::rdata;
using isc::dns::rdata::generic::detail::CharString;
-using isc::dns::rdata::generic::detail::strToCharString;
+using isc::dns::rdata::generic::detail::bufferToCharString;
+using isc::dns::rdata::generic::detail::stringToCharString;
+using isc::dns::rdata::generic::detail::charStringToString;
+using isc::dns::rdata::generic::detail::compareCharStrings;
using isc::util::unittests::matchWireData;
namespace {
@@ -60,19 +65,19 @@ createStringRegion(const std::string& str) {
TEST_F(CharStringTest, normalConversion) {
uint8_t tmp[3]; // placeholder for expected sequence
- strToCharString(str_region, chstr);
+ stringToCharString(str_region, chstr);
matchWireData(test_charstr, sizeof(test_charstr), &chstr[0], chstr.size());
// Empty string
chstr.clear();
- strToCharString(createStringRegion(""), chstr);
+ stringToCharString(createStringRegion(""), chstr);
tmp[0] = 0;
matchWireData(tmp, 1, &chstr[0], chstr.size());
// Possible largest char string
chstr.clear();
std::string long_str(255, 'x');
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
std::vector<uint8_t> expected;
expected.push_back(255); // len of char string
expected.insert(expected.end(), long_str.begin(), long_str.end());
@@ -83,32 +88,32 @@ TEST_F(CharStringTest, normalConversion) {
chstr.clear();
long_str.at(254) = '\\'; // replace the last 'x' with '\'
long_str.append("120"); // 'x' = 120
- strToCharString(createStringRegion(long_str), chstr);
+ stringToCharString(createStringRegion(long_str), chstr);
matchWireData(&expected[0], expected.size(), &chstr[0], chstr.size());
// Escaped '\'
chstr.clear();
tmp[0] = 1;
tmp[1] = '\\';
- strToCharString(createStringRegion("\\\\"), chstr);
+ stringToCharString(createStringRegion("\\\\"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Boundary values for \DDD
chstr.clear();
tmp[0] = 1;
tmp[1] = 0;
- strToCharString(createStringRegion("\\000"), chstr);
+ stringToCharString(createStringRegion("\\000"), chstr);
matchWireData(tmp, 2, &chstr[0], chstr.size());
chstr.clear();
- strToCharString(createStringRegion("\\255"), chstr);
+ stringToCharString(createStringRegion("\\255"), chstr);
tmp[0] = 1;
tmp[1] = 255;
matchWireData(tmp, 2, &chstr[0], chstr.size());
// Another digit follows DDD; it shouldn't cause confusion
chstr.clear();
- strToCharString(createStringRegion("\\2550"), chstr);
+ stringToCharString(createStringRegion("\\2550"), chstr);
tmp[0] = 2; // string len is now 2
tmp[2] = '0';
matchWireData(tmp, 3, &chstr[0], chstr.size());
@@ -116,13 +121,13 @@ TEST_F(CharStringTest, normalConversion) {
TEST_F(CharStringTest, badConversion) {
// string cannot exceed 255 bytes
- EXPECT_THROW(strToCharString(createStringRegion(std::string(256, 'a')),
- chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion(std::string(256, 'a')),
+ chstr),
CharStringTooLong);
// input string ending with (non escaped) '\'
chstr.clear();
- EXPECT_THROW(strToCharString(createStringRegion("foo\\"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("foo\\"), chstr),
InvalidRdataText);
}
@@ -130,18 +135,118 @@ TEST_F(CharStringTest, badDDD) {
// Check various type of bad form of \DDD
// Not a number
- EXPECT_THROW(strToCharString(createStringRegion("\\1a2"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\1a2"), chstr),
InvalidRdataText);
- EXPECT_THROW(strToCharString(createStringRegion("\\12a"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\12a"), chstr),
InvalidRdataText);
// Not in the range of uint8_t
- EXPECT_THROW(strToCharString(createStringRegion("\\256"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\256"), chstr),
InvalidRdataText);
// Short buffer
- EXPECT_THROW(strToCharString(createStringRegion("\\42"), chstr),
+ EXPECT_THROW(stringToCharString(createStringRegion("\\42"), chstr),
InvalidRdataText);
}
+const struct TestData {
+ const char *data;
+ const char *expected;
+} conversion_data[] = {
+ {"Test\"Test", "Test\\\"Test"},
+ {"Test;Test", "Test\\;Test"},
+ {"Test\\Test", "Test\\\\Test"},
+ {"Test\x1fTest", "Test\\031Test"},
+ {"Test ~ Test", "Test ~ Test"},
+ {"Test\x7fTest", "Test\\127Test"},
+ {NULL, NULL}
+};
+
+TEST_F(CharStringTest, charStringToString) {
+ for (const TestData* cur = conversion_data; cur->data != NULL; ++cur) {
+ uint8_t idata[32];
+ size_t length = std::strlen(cur->data);
+ // length (1 byte) + string (length bytes)
+ assert(sizeof(idata) > length);
+ idata[0] = static_cast<uint8_t>(length);
+ std::memcpy(idata + 1, cur->data, length);
+ const CharString test_data(idata, idata + length + 1);
+ EXPECT_EQ(cur->expected, charStringToString(test_data));
+ }
+}
+
+TEST_F(CharStringTest, bufferToCharString) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ size_t read = bufferToCharString(buf, chstr_size, chstr);
+
+ EXPECT_EQ(chstr_size, read);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+}
+
+TEST_F(CharStringTest, bufferToCharString_bad) {
+ const size_t chstr_size = sizeof(test_charstr);
+ isc::util::InputBuffer buf(test_charstr, chstr_size);
+ // Set valid data in both so we can make sure the charstr is not
+ // modified
+ bufferToCharString(buf, chstr_size, chstr);
+ ASSERT_EQ("Test String", charStringToString(chstr));
+
+ // Should be at end of buffer now, so it should fail
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // reset and try to read with too low rdata_len
+ buf.setPosition(0);
+ EXPECT_THROW(bufferToCharString(buf, chstr_size - 1, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+ // set internal charstring len too high
+ const uint8_t test_charstr_err[] = {
+ sizeof("Test String") + 1,
+ 'T', 'e', 's', 't', ' ', 'S', 't', 'r', 'i', 'n', 'g'
+ };
+ buf = isc::util::InputBuffer(test_charstr_err, sizeof(test_charstr_err));
+ EXPECT_THROW(bufferToCharString(buf, chstr_size, chstr),
+ DNSMessageFORMERR);
+ EXPECT_EQ("Test String", charStringToString(chstr));
+
+}
+
+
+
+TEST_F(CharStringTest, compareCharString) {
+ CharString charstr;
+ CharString charstr2;
+ CharString charstr_small1;
+ CharString charstr_small2;
+ CharString charstr_large1;
+ CharString charstr_large2;
+ CharString charstr_empty;
+
+ stringToCharString(createStringRegion("test string"), charstr);
+ stringToCharString(createStringRegion("test string"), charstr2);
+ stringToCharString(createStringRegion("test strin"), charstr_small1);
+ stringToCharString(createStringRegion("test strina"), charstr_small2);
+ stringToCharString(createStringRegion("test stringa"), charstr_large1);
+ stringToCharString(createStringRegion("test strinz"), charstr_large2);
+
+ EXPECT_EQ(0, compareCharStrings(charstr, charstr2));
+ EXPECT_EQ(0, compareCharStrings(charstr2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small1));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_small2));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large1));
+ EXPECT_EQ(-1, compareCharStrings(charstr, charstr_large2));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small1, charstr));
+ EXPECT_EQ(-1, compareCharStrings(charstr_small2, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large1, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr_large2, charstr));
+
+ EXPECT_EQ(-1, compareCharStrings(charstr_empty, charstr));
+ EXPECT_EQ(1, compareCharStrings(charstr, charstr_empty));
+ EXPECT_EQ(0, compareCharStrings(charstr_empty, charstr_empty));
+}
+
} // unnamed namespace
diff --git a/src/lib/dns/tests/rdata_hinfo_unittest.cc b/src/lib/dns/tests/rdata_hinfo_unittest.cc
index f592066..7be2cb6 100644
--- a/src/lib/dns/tests/rdata_hinfo_unittest.cc
+++ b/src/lib/dns/tests/rdata_hinfo_unittest.cc
@@ -51,10 +51,9 @@ TEST_F(Rdata_HINFO_Test, createFromText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(string("Pentium"), hinfo.getCPU());
EXPECT_EQ(string("Linux"), hinfo.getOS());
-
// Test the text with double quotes in the middle of string
HINFO hinfo1(hinfo_str1);
- EXPECT_EQ(string("Pen\"tium"), hinfo1.getCPU());
+ EXPECT_EQ(string("Pen\\\"tium"), hinfo1.getCPU());
}
TEST_F(Rdata_HINFO_Test, badText) {
@@ -96,11 +95,20 @@ TEST_F(Rdata_HINFO_Test, createFromLexer) {
EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
"\"Pentium\" \"Linux\" "
"\"Computer\""));
+ EXPECT_FALSE(test::createRdataUsingLexer(RRType::HINFO(), RRClass::IN(),
+ "\"Pentium\""));
}
TEST_F(Rdata_HINFO_Test, toText) {
HINFO hinfo(hinfo_str);
EXPECT_EQ(hinfo_str, hinfo.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("\"a\" \"b\"", HINFO("a b").toText());
+ // will not add additional quotes
+ EXPECT_EQ("\"a\" \"b\"", HINFO("\"a\" \"b\"").toText());
+ // And make sure escaped quotes and spaces are left intact
+ EXPECT_EQ("\"a\\\"\" \"b c\"", HINFO("\"a\\\"\" \"b c\"").toText());
}
TEST_F(Rdata_HINFO_Test, toWire) {
@@ -135,4 +143,17 @@ TEST_F(Rdata_HINFO_Test, compare) {
EXPECT_EQ(-1, hinfo.compare(HINFO(hinfo_str_large2)));
}
+// Copy/assign test
+TEST_F(Rdata_HINFO_Test, copy) {
+ HINFO hinfo(hinfo_str);
+ HINFO hinfo2(hinfo);
+ HINFO hinfo3 = hinfo;
+
+ EXPECT_EQ(0, hinfo.compare(hinfo2));
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+
+ hinfo3 = hinfo;
+ EXPECT_EQ(0, hinfo.compare(hinfo3));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_naptr_unittest.cc b/src/lib/dns/tests/rdata_naptr_unittest.cc
index fed98cb..d828e73 100644
--- a/src/lib/dns/tests/rdata_naptr_unittest.cc
+++ b/src/lib/dns/tests/rdata_naptr_unittest.cc
@@ -99,10 +99,12 @@ TEST_F(Rdata_NAPTR_Test, badText) {
// Order or preference cannot be missed
EXPECT_THROW(const NAPTR naptr("10 \"S\" SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- // Fields must be seperated by spaces
+ // Unquoted fields must be seperated by spaces
EXPECT_THROW(const NAPTR naptr("100 10S SIP \"\" _sip._udp.example.com."),
InvalidRdataText);
- EXPECT_THROW(const NAPTR naptr("100 10 \"S\"\"SIP\" \"\" _sip._udp.example.com."),
+ EXPECT_THROW(const NAPTR naptr("10010 \"S\" \"SIP\" \"\" _sip._udp.example.com."),
+ InvalidRdataText);
+ EXPECT_THROW(const NAPTR naptr("100 10 SSIP \"\" _sip._udp.example.com."),
InvalidRdataText);
// Field cannot be missing
EXPECT_THROW(const NAPTR naptr("100 10 \"S\""), InvalidRdataText);
@@ -128,6 +130,26 @@ TEST_F(Rdata_NAPTR_Test, createFromWire) {
EXPECT_EQ(Name("_sip._udp.example.com."), naptr.getReplacement());
}
+TEST_F(Rdata_NAPTR_Test, createFromWireTooLongDataLen) {
+ static uint8_t naptr_rdata_long[] = {
+ 0x00,0x0a,0x00,0x64,0x01,0x53,0x07,0x53,0x49,0x50,0x2b,0x44,0x32,0x55,
+ 0x00,0x04,0x5f,0x73,0x69,0x70,0x04,0x5f,0x75,0x64,0x70,0x07,0x65,0x78,
+ 0x61,0x6d,0x70,0x6c,0x65,0x03,0x63,0x6f,0x6d,0x00,0xff,0xff,0xff,0xff};
+ InputBuffer input_buffer(naptr_rdata_long, sizeof(naptr_rdata_long));
+ EXPECT_THROW(NAPTR naptr(input_buffer, sizeof(naptr_rdata_long)),
+ isc::dns::DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_NAPTR_Test, createFromWireTooShortDataLen) {
+ // missing data (just set rdata_len too low)
+ for (size_t i = 0; i < sizeof(naptr_rdata); ++i) {
+ // Just use existing correct buffer but set rdata_len too low
+ InputBuffer input_buffer(naptr_rdata, sizeof(naptr_rdata));
+ EXPECT_THROW(NAPTR naptr(input_buffer, i),
+ isc::dns::DNSMessageFORMERR);
+ }
+}
+
TEST_F(Rdata_NAPTR_Test, createFromLexer) {
const NAPTR rdata_naptr(naptr_str);
@@ -161,6 +183,14 @@ TEST_F(Rdata_NAPTR_Test, toWireRenderer) {
TEST_F(Rdata_NAPTR_Test, toText) {
NAPTR naptr(naptr_str);
EXPECT_EQ(naptr_str, naptr.toText());
+
+ // will add quotes even if they were not in the original input
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 S SIP+D2U .* _sip._udp.example.com.").toText());
+ // will not add additional quotes
+ EXPECT_EQ("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.",
+ NAPTR("10 100 \"S\" \"SIP+D2U\" \".*\" _sip._udp.example.com.")
+ .toText());
}
TEST_F(Rdata_NAPTR_Test, compare) {
@@ -177,16 +207,38 @@ TEST_F(Rdata_NAPTR_Test, compare) {
NAPTR naptr_large5(naptr_str_large5);
EXPECT_EQ(0, naptr.compare(NAPTR(naptr_str)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small1)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small2)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small3)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small4)));
- EXPECT_EQ(1, naptr.compare(NAPTR(naptr_str_small5)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large1)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large2)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large3)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large4)));
- EXPECT_EQ(-1, naptr.compare(NAPTR(naptr_str_large5)));
+ EXPECT_EQ(1, naptr.compare(naptr_small1));
+ EXPECT_EQ(1, naptr.compare(naptr_small2));
+ EXPECT_EQ(1, naptr.compare(naptr_small3));
+ EXPECT_EQ(1, naptr.compare(naptr_small4));
+ EXPECT_EQ(1, naptr.compare(naptr_small5));
+ EXPECT_EQ(-1, naptr.compare(naptr_large1));
+ EXPECT_EQ(-1, naptr.compare(naptr_large2));
+ EXPECT_EQ(-1, naptr.compare(naptr_large3));
+ EXPECT_EQ(-1, naptr.compare(naptr_large4));
+ EXPECT_EQ(-1, naptr.compare(naptr_large5));
+ EXPECT_EQ(-1, naptr_small1.compare(naptr));
+ EXPECT_EQ(-1, naptr_small2.compare(naptr));
+ EXPECT_EQ(-1, naptr_small3.compare(naptr));
+ EXPECT_EQ(-1, naptr_small4.compare(naptr));
+ EXPECT_EQ(-1, naptr_small5.compare(naptr));
+ EXPECT_EQ(1, naptr_large1.compare(naptr));
+ EXPECT_EQ(1, naptr_large2.compare(naptr));
+ EXPECT_EQ(1, naptr_large3.compare(naptr));
+ EXPECT_EQ(1, naptr_large4.compare(naptr));
+ EXPECT_EQ(1, naptr_large5.compare(naptr));
+}
+
+TEST_F(Rdata_NAPTR_Test, copy) {
+ NAPTR naptr(naptr_str);
+ NAPTR naptr2(naptr);
+ NAPTR naptr3 = naptr;
+
+ EXPECT_EQ(0, naptr.compare(naptr2));
+ EXPECT_EQ(0, naptr.compare(naptr3));
+
+ naptr3 = naptr;
+ EXPECT_EQ(0, naptr.compare(naptr3));
}
}
diff --git a/src/lib/dns/tests/rdata_soa_unittest.cc b/src/lib/dns/tests/rdata_soa_unittest.cc
index 52f0601..ed1c29f 100644
--- a/src/lib/dns/tests/rdata_soa_unittest.cc
+++ b/src/lib/dns/tests/rdata_soa_unittest.cc
@@ -33,15 +33,129 @@ using namespace isc::dns::rdata;
namespace {
class Rdata_SOA_Test : public RdataTest {
protected:
- Rdata_SOA_Test() : rdata_soa(Name("ns.example.com"),
- Name("root.example.com"),
- 2010012601, 3600, 300, 3600000, 1200)
+ Rdata_SOA_Test() :
+ rdata_soa(Name("ns.example.com"),
+ Name("root.example.com"),
+ 2010012601, 3600, 300, 3600000, 1200)
{}
+
+ // Common check to see if the given text can be used to construct SOA
+ // Rdata that is identical rdata_soa.
+ void checkFromText(const char* soa_txt, const Name* origin = NULL) {
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+
+ if (origin == NULL) {
+ // from-string constructor works correctly only when origin
+ // is NULL (by its nature).
+ EXPECT_EQ(0, generic::SOA(soa_txt).compare(rdata_soa));
+ }
+ EXPECT_EQ(0, generic::SOA(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb).compare(rdata_soa));
+ }
+
+ // Common check if given text (which is invalid as SOA RDATA) is rejected
+ // with the specified type of exception: ExForString is the expected
+ // exception for the "from string" constructor; ExForLexer is for the
+ // constructor with master lexer.
+ template <typename ExForString, typename ExForLexer>
+ void checkFromBadTexxt(const char* soa_txt, const Name* origin = NULL) {
+ EXPECT_THROW(generic::SOA soa(soa_txt), ExForString);
+
+ std::stringstream ss(soa_txt);
+ MasterLexer lexer;
+ lexer.pushSource(ss);
+ EXPECT_THROW(generic::SOA soa(lexer, origin, MasterLoader::DEFAULT,
+ loader_cb), ExForLexer);
+ }
+
const generic::SOA rdata_soa;
};
TEST_F(Rdata_SOA_Test, createFromText) {
- //TBD
+ // A simple case.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200");
+
+ // Beginning and trailing space are ignored.
+ checkFromText(" ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200 ");
+
+ // using extended TTL-like form for some parameters.
+ checkFromText("ns.example.com. root.example.com. "
+ "2010012601 1H 5M 1000H 20M");
+
+ // multi-line.
+ checkFromText("ns.example.com. (root.example.com.\n"
+ "2010012601 1H 5M 1000H) 20M");
+
+ // relative names for MNAME and RNAME with a separate origin (lexer
+ // version only)
+ const Name origin("example.com");
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M", &origin);
+
+ // with the '@' notation with a separate origin (lexer version only)
+ const Name full_mname("ns.example.com");
+ checkFromText("@ root.example.com. 2010012601 1H 5M 1000H 20M",
+ &full_mname);
+
+ // bad MNAME/RNAMEs
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ "bad..example. . 2010012601 1H 5M 1000H 20M");
+ checkFromBadTexxt<EmptyLabel, EmptyLabel>(
+ ". bad..example. 2010012601 1H 5M 1000H 20M");
+
+ // Names shouldn't be quoted. (Note: on completion of #2534, the resulting
+ // exception will be different).
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ "\".\" . 0 0 0 0 0");
+ checkFromBadTexxt<MissingNameOrigin, MissingNameOrigin>(
+ ". \".\" 0 0 0 0 0");
+
+ // Missing MAME or RNAME: for the string version, the serial would be
+ // tried as RNAME and result in "not absolute". For the lexer version,
+ // it reaches the end-of-line, missing min TTL.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". 2010012601 0 0 0 0", &Name::ROOT_NAME());
+
+ // bad serial. the string version converts lexer error to
+ // InvalidRdataText.
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . bad 0 0 0 0");
+
+ // bad serial; exceeding the uint32_t range (4294967296 = 2^32)
+ checkFromBadTexxt<InvalidRdataText, MasterLexer::LexerError>(
+ ". . 4294967296 0 0 0 0");
+
+ // Bad format for other numeric parameters. These will be tried as a TTL,
+ // and result in an exception there.
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 bad 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 4294967296 0 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 bad 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 4294967296 0 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 bad 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 4294967296 0");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(". . 2010012601 0 0 0 bad");
+ checkFromBadTexxt<InvalidRRTTL, InvalidRRTTL>(
+ ". . 2010012601 0 0 0 4294967296");
+
+ // No space between RNAME and serial. This case is the same as missing
+ // M/RNAME.
+ checkFromBadTexxt<MissingNameOrigin, MasterLexer::LexerError>(
+ ". example.0 0 0 0 0", &Name::ROOT_NAME());
+
+ // Extra parameter. string version immediately detects the error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0 extra"), InvalidRdataText);
+ // Likewise. Redundant newline is also considered an error.
+ EXPECT_THROW(generic::SOA soa(". . 0 0 0 0 0\n"), InvalidRdataText);
+ EXPECT_THROW(generic::SOA soa("\n. . 0 0 0 0 0"), InvalidRdataText);
+ // lexer version defers the check to the upper layer (we pass origin
+ // to skip the check with the string version).
+ checkFromText("ns root 2010012601 1H 5M 1000H 20M extra", &origin);
}
TEST_F(Rdata_SOA_Test, createFromWire) {
@@ -97,4 +211,42 @@ TEST_F(Rdata_SOA_Test, getMinimum) {
0, 0, 0, 0, 0x80706050).getMinimum());
}
+void
+compareCheck(const generic::SOA& small, const generic::SOA& large) {
+ EXPECT_GT(0, small.compare(large));
+ EXPECT_LT(0, large.compare(small));
+}
+
+TEST_F(Rdata_SOA_Test, compare) {
+ // Check simple equivalence
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "ns.example.com. root.example.com. "
+ "2010012601 3600 300 3600000 1200")));
+ // Check name comparison is case insensitive
+ EXPECT_EQ(0, rdata_soa.compare(generic::SOA(
+ "NS.example.com. root.EXAMPLE.com. "
+ "2010012601 3600 300 3600000 1200")));
+
+ // Check names are compared in the RDATA comparison semantics (different
+ // from DNSSEC ordering for owner names)
+ compareCheck(generic::SOA("a.example. . 0 0 0 0 0"),
+ generic::SOA("example. . 0 0 0 0 0"));
+ compareCheck(generic::SOA(". a.example. 0 0 0 0 0"),
+ generic::SOA(". example. 0 0 0 0 0"));
+
+ // Compare other numeric fields: 1076895760 = 0x40302010,
+ // 270544960 = 0x10203040. These are chosen to make sure that machine
+ // endian doesn't confuse the comparison results.
+ compareCheck(generic::SOA(". . 270544960 0 0 0 0"),
+ generic::SOA(". . 1076895760 0 0 0 0"));
+ compareCheck(generic::SOA(". . 0 270544960 0 0 0"),
+ generic::SOA(". . 0 1076895760 0 0 0"));
+ compareCheck(generic::SOA(". . 0 0 270544960 0 0"),
+ generic::SOA(". . 0 0 1076895760 0 0"));
+ compareCheck(generic::SOA(". . 0 0 0 270544960 0"),
+ generic::SOA(". . 0 0 0 1076895760 0"));
+ compareCheck(generic::SOA(". . 0 0 0 0 270544960"),
+ generic::SOA(". . 0 0 0 0 1076895760"));
+}
+
}
diff --git a/src/lib/dns/tests/rdata_txt_like_unittest.cc b/src/lib/dns/tests/rdata_txt_like_unittest.cc
index d045875..cb3c44d 100644
--- a/src/lib/dns/tests/rdata_txt_like_unittest.cc
+++ b/src/lib/dns/tests/rdata_txt_like_unittest.cc
@@ -57,7 +57,6 @@ template<class TXT_LIKE>
class Rdata_TXT_LIKE_Test : public RdataTest {
protected:
Rdata_TXT_LIKE_Test() :
- loader_cb(MasterLoaderCallbacks::getNullCallbacks()),
wiredata_longesttxt(256, 'a'),
rdata_txt_like("Test-String"),
rdata_txt_like_empty("\"\""),
@@ -67,7 +66,6 @@ protected:
}
protected:
- MasterLoaderCallbacks loader_cb;
vector<uint8_t> wiredata_longesttxt;
const TXT_LIKE rdata_txt_like;
const TXT_LIKE rdata_txt_like_empty;
@@ -334,6 +332,24 @@ TYPED_TEST(Rdata_TXT_LIKE_Test, toWireRenderer) {
TYPED_TEST(Rdata_TXT_LIKE_Test, toText) {
EXPECT_EQ("\"Test-String\"", this->rdata_txt_like.toText());
+ EXPECT_EQ("\"\"", this->rdata_txt_like_empty.toText());
+ EXPECT_EQ("\"Test-String\"", this->rdata_txt_like_quoted.toText());
+
+ // Check escape behavior
+ const TypeParam double_quotes("Test-String\"Test-String\"");
+ EXPECT_EQ("\"Test-String\\\"Test-String\\\"\"", double_quotes.toText());
+ const TypeParam semicolon("Test-String\\;Test-String");
+ EXPECT_EQ("\"Test-String\\;Test-String\"", semicolon.toText());
+ const TypeParam backslash("Test-String\\\\Test-String");
+ EXPECT_EQ("\"Test-String\\\\Test-String\"", backslash.toText());
+ const TypeParam before_x20("Test-String\\031Test-String");
+ EXPECT_EQ("\"Test-String\\031Test-String\"", before_x20.toText());
+ const TypeParam from_x20_to_x7e("\"Test-String ~ Test-String\"");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e.toText());
+ const TypeParam from_x20_to_x7e_2("Test-String\\032\\126\\032Test-String");
+ EXPECT_EQ("\"Test-String ~ Test-String\"", from_x20_to_x7e_2.toText());
+ const TypeParam after_x7e("Test-String\\127Test-String");
+ EXPECT_EQ("\"Test-String\\127Test-String\"", after_x7e.toText());
}
TYPED_TEST(Rdata_TXT_LIKE_Test, assignment) {
diff --git a/src/lib/dns/tests/rdata_unittest.cc b/src/lib/dns/tests/rdata_unittest.cc
index 95f50e7..67eae2f 100644
--- a/src/lib/dns/tests/rdata_unittest.cc
+++ b/src/lib/dns/tests/rdata_unittest.cc
@@ -41,7 +41,8 @@ namespace isc {
namespace dns {
namespace rdata {
RdataTest::RdataTest() :
- obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0"))
+ obuffer(0), rdata_nomatch(createRdata(RRType(0), RRClass(1), "\\# 0")),
+ loader_cb(MasterLoaderCallbacks::getNullCallbacks())
{}
RdataPtr
diff --git a/src/lib/dns/tests/rdata_unittest.h b/src/lib/dns/tests/rdata_unittest.h
index af19311..3f1dbb3 100644
--- a/src/lib/dns/tests/rdata_unittest.h
+++ b/src/lib/dns/tests/rdata_unittest.h
@@ -42,6 +42,7 @@ protected:
/// used to test the compare() method against a well-known RR type.
RdataPtr rdata_nomatch;
MasterLexer lexer;
+ MasterLoaderCallbacks loader_cb;
};
namespace test {
diff --git a/src/lib/dns/tests/rrclass_unittest.cc b/src/lib/dns/tests/rrclass_unittest.cc
index 6156be5..11f1c54 100644
--- a/src/lib/dns/tests/rrclass_unittest.cc
+++ b/src/lib/dns/tests/rrclass_unittest.cc
@@ -59,7 +59,7 @@ RRClassTest::rrclassFactoryFromWire(const char* datafile) {
return (RRClass(buffer));
}
-TEST_F(RRClassTest, fromText) {
+TEST_F(RRClassTest, fromTextConstructor) {
EXPECT_EQ("IN", RRClass("IN").toText());
EXPECT_EQ("CH", RRClass("CH").toText());
@@ -96,6 +96,14 @@ TEST_F(RRClassTest, toText) {
EXPECT_EQ("CLASS65000", RRClass(65000).toText());
}
+TEST_F(RRClassTest, createFromText) {
+ const MaybeRRClass rrclass("IN");
+ EXPECT_TRUE(rrclass);
+ EXPECT_EQ("IN", rrclass->toText());
+ EXPECT_TRUE(RRClass::createFromText("CH"));
+ EXPECT_FALSE(RRClass::createFromText("ZZ"));
+}
+
TEST_F(RRClassTest, toWireBuffer) {
rrclass_1.toWire(obuffer);
rrclass_0x80.toWire(obuffer);
diff --git a/src/lib/dns/tests/rrset_collection_unittest.cc b/src/lib/dns/tests/rrset_collection_unittest.cc
new file mode 100644
index 0000000..e17e9e7
--- /dev/null
+++ b/src/lib/dns/tests/rrset_collection_unittest.cc
@@ -0,0 +1,246 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/rrset_collection.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <list>
+#include <fstream>
+
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace std;
+
+namespace {
+
+class RRsetCollectionTest : public ::testing::Test {
+public:
+ RRsetCollectionTest() :
+ rrclass("IN"),
+ origin("example.org"),
+ collection(TEST_DATA_SRCDIR "/example.org", origin, rrclass)
+ {}
+
+ const RRClass rrclass;
+ const Name origin;
+ RRsetCollection collection;
+};
+
+TEST_F(RRsetCollectionTest, istreamConstructor) {
+ std::ifstream fs(TEST_DATA_SRCDIR "/example.org");
+ RRsetCollection collection2(fs, origin, rrclass);
+
+ RRsetCollectionBase::Iterator iter = collection.begin();
+ RRsetCollectionBase::Iterator iter2 = collection2.begin();
+ while (iter != collection.end()) {
+ ASSERT_TRUE(iter2 != collection2.end());
+ EXPECT_EQ((*iter).toText(), (*iter2).toText());
+ ++iter;
+ ++iter2;
+ }
+ ASSERT_TRUE(iter2 == collection2.end());
+}
+
+template <typename T, typename TP>
+void doFind(T& collection, const RRClass& rrclass) {
+ // Test the find() that returns ConstRRsetPtr
+ TP rrset = collection.find(Name("www.example.org"), rrclass, RRType::A());
+ EXPECT_TRUE(rrset);
+ EXPECT_EQ(RRType::A(), rrset->getType());
+ EXPECT_EQ(RRTTL(3600), rrset->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset->getClass());
+ EXPECT_EQ(Name("www.example.org"), rrset->getName());
+
+ // foo.example.org doesn't exist
+ rrset = collection.find(Name("foo.example.org"), rrclass, RRType::A());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, but not with MX
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::MX());
+ EXPECT_FALSE(rrset);
+
+ // www.example.org exists, with AAAA
+ rrset = collection.find(Name("www.example.org"), rrclass, RRType::AAAA());
+ EXPECT_TRUE(rrset);
+
+ // www.example.org with AAAA does not exist in RRClass::CH()
+ rrset = collection.find(Name("www.example.org"), RRClass::CH(),
+ RRType::AAAA());
+ EXPECT_FALSE(rrset);
+}
+
+TEST_F(RRsetCollectionTest, findConst) {
+ // Test the find() that returns ConstRRsetPtr
+ const RRsetCollection& ccln = collection;
+ doFind<const RRsetCollection, ConstRRsetPtr>(ccln, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, find) {
+ // Test the find() that returns RRsetPtr
+ doFind<RRsetCollection, RRsetPtr>(collection, rrclass);
+}
+
+void
+doAddAndRemove(RRsetCollection& collection, const RRClass& rrclass) {
+ // foo.example.org/A doesn't exist
+ RRsetPtr rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Add foo.example.org/A
+ RRsetPtr rrset(new BasicRRset(Name("foo.example.org"), rrclass, RRType::A(),
+ RRTTL(7200)));
+ rrset->addRdata(in::A("192.0.2.1"));
+ collection.addRRset(rrset);
+
+ // foo.example.org/A should now exist
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_TRUE(rrset_found);
+ EXPECT_EQ(RRType::A(), rrset_found->getType());
+ EXPECT_EQ(RRTTL(7200), rrset_found->getTTL());
+ EXPECT_EQ(RRClass("IN"), rrset_found->getClass());
+ EXPECT_EQ(Name("foo.example.org"), rrset_found->getName());
+
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Adding a duplicate RRset must throw.
+ EXPECT_THROW({
+ collection.addRRset(rrset);
+ }, isc::InvalidParameter);
+
+ // Remove foo.example.org/A, which should pass
+ EXPECT_TRUE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+ // foo.example.org/A should not exist now
+ rrset_found = collection.find(Name("foo.example.org"), rrclass,
+ RRType::A());
+ EXPECT_FALSE(rrset_found);
+
+ // Removing foo.example.org/A should fail now
+ EXPECT_FALSE(collection.removeRRset(Name("foo.example.org"),
+ rrclass, RRType::A()));
+}
+
+TEST_F(RRsetCollectionTest, addAndRemove) {
+ doAddAndRemove(collection, rrclass);
+}
+
+TEST_F(RRsetCollectionTest, empty) {
+ RRsetCollection cln;
+
+ // Here, cln is empty.
+ EXPECT_TRUE(cln.end() == cln.begin());
+
+ doAddAndRemove(cln, rrclass);
+
+ // cln should be empty again here, after the add and remove
+ // operations.
+ EXPECT_TRUE(cln.end() == cln.begin());
+}
+
+TEST_F(RRsetCollectionTest, iteratorTest) {
+ // The collection must not be empty.
+ EXPECT_TRUE(collection.end() != collection.begin());
+
+ // Here, we just count the records and do some basic tests on them.
+ size_t count = 0;
+ for (RRsetCollection::Iterator it = collection.begin();
+ it != collection.end(); ++it) {
+ ++count;
+ const AbstractRRset& rrset = *it;
+ EXPECT_EQ(rrclass, rrset.getClass());
+ EXPECT_EQ(RRTTL(3600), rrset.getTTL());
+ }
+
+ // example.org master file has SOA, NS, A, AAAA
+ EXPECT_EQ(4, count);
+}
+
+// This is a dummy class which is used in iteratorCompareDifferent test
+// to compare iterators from different RRsetCollectionBase
+// implementations.
+class MyRRsetCollection : public RRsetCollectionBase {
+public:
+ MyRRsetCollection()
+ {}
+
+ virtual isc::dns::ConstRRsetPtr find(const isc::dns::Name&,
+ const isc::dns::RRClass&,
+ const isc::dns::RRType&) const {
+ return (ConstRRsetPtr());
+ }
+
+ typedef std::list<isc::dns::RRset> MyCollection;
+
+protected:
+ class MyIter : public RRsetCollectionBase::Iter {
+ public:
+ MyIter(MyCollection::iterator& iter) :
+ iter_(iter)
+ {}
+
+ virtual const isc::dns::AbstractRRset& getValue() {
+ return (*iter_);
+ }
+
+ virtual IterPtr getNext() {
+ MyCollection::iterator it = iter_;
+ ++it;
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual bool equals(Iter& other) {
+ const MyIter* other_real = dynamic_cast<MyIter*>(&other);
+ if (other_real == NULL) {
+ return (false);
+ }
+ return (iter_ == other_real->iter_);
+ }
+
+ private:
+ MyCollection::iterator iter_;
+ };
+
+ virtual RRsetCollectionBase::IterPtr getBeginning() {
+ MyCollection::iterator it = dummy_list_.begin();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+ virtual RRsetCollectionBase::IterPtr getEnd() {
+ MyCollection::iterator it = dummy_list_.end();
+ return (RRsetCollectionBase::IterPtr(new MyIter(it)));
+ }
+
+private:
+ MyCollection dummy_list_;
+};
+
+TEST_F(RRsetCollectionTest, iteratorCompareDifferent) {
+ // Create objects of two different RRsetCollectionBase
+ // implementations.
+ RRsetCollection cln1;
+ MyRRsetCollection cln2;
+
+ // Comparing two iterators from different RRsetCollectionBase
+ // implementations must not throw.
+ EXPECT_TRUE(cln2.begin() != cln1.begin());
+ EXPECT_TRUE(cln1.end() != cln2.end());
+}
+
+} // namespace
diff --git a/src/lib/dns/tests/zone_checker_unittest.cc b/src/lib/dns/tests/zone_checker_unittest.cc
new file mode 100644
index 0000000..dbe204d
--- /dev/null
+++ b/src/lib/dns/tests/zone_checker_unittest.cc
@@ -0,0 +1,352 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+#include <dns/rrset.h>
+#include <dns/rrtype.h>
+#include <dns/rrttl.h>
+#include <dns/rdataclass.h>
+#include <dns/rrset_collection.h>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+
+#include <algorithm>
+#include <string>
+#include <sstream>
+#include <vector>
+
+using isc::Unexpected;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+
+const char* const soa_txt = "ns.example.com. root.example.com. 0 0 0 0 0";
+const char* const ns_txt1 = "ns.example.com.";
+const char* const ns_a_txt1 = "192.0.2.1";
+const char* const ns_txt2 = "ns2.example.com.";
+const char* const ns_a_txt2 = "192.0.2.2";
+
+class ZoneCheckerTest : public ::testing::Test {
+protected:
+ ZoneCheckerTest() :
+ zname_("example.com"), zclass_(RRClass::IN()),
+ soa_(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60))),
+ ns_(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60))),
+ callbacks_(boost::bind(&ZoneCheckerTest::callback, this, _1, true),
+ boost::bind(&ZoneCheckerTest::callback, this, _1, false))
+ {
+ std::stringstream ss;
+ ss << "example.com. 60 IN SOA " << soa_txt << "\n";
+ ss << "example.com. 60 IN NS " << ns_txt1 << "\n";
+ ss << "ns.example.com. 60 IN A " << ns_a_txt1 << "\n";
+ ss << "ns2.example.com. 60 IN A " << ns_a_txt2 << "\n";
+ rrsets_.reset(new RRsetCollection(ss, zname_, zclass_));
+ }
+
+public:
+ // This one is passed to boost::bind. Some compilers seem to require
+ // it be public.
+ void callback(const std::string& reason, bool is_error) {
+ if (is_error) {
+ errors_.push_back(reason);
+ } else {
+ warns_.push_back(reason);
+ }
+ }
+
+protected:
+ // Check stored issue messages with expected ones. Clear vectors so
+ // the caller can check other cases.
+ void checkIssues() {
+ EXPECT_EQ(expected_errors_.size(), errors_.size());
+ for (int i = 0; i < std::min(expected_errors_.size(), errors_.size());
+ ++i) {
+ // The actual message should begin with the expected message.
+ EXPECT_EQ(0, errors_[0].find(expected_errors_[0]))
+ << "actual message: " << errors_[0] << " expected: " <<
+ expected_errors_[0];
+ }
+ EXPECT_EQ(expected_warns_.size(), warns_.size());
+ for (int i = 0; i < std::min(expected_warns_.size(), warns_.size());
+ ++i) {
+ EXPECT_EQ(0, warns_[0].find(expected_warns_[0]))
+ << "actual message: " << warns_[0] << " expected: " <<
+ expected_warns_[0];
+ }
+
+ errors_.clear();
+ expected_errors_.clear();
+ warns_.clear();
+ expected_warns_.clear();
+ }
+
+ const Name zname_;
+ const RRClass zclass_;
+ boost::scoped_ptr<RRsetCollection> rrsets_;
+ RRsetPtr soa_;
+ RRsetPtr ns_;
+ std::vector<std::string> errors_;
+ std::vector<std::string> warns_;
+ std::vector<std::string> expected_errors_;
+ std::vector<std::string> expected_warns_;
+ ZoneCheckerCallbacks callbacks_;
+};
+
+TEST_F(ZoneCheckerTest, checkGood) {
+ // Checking a valid case. No errors or warnings should be reported.
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Multiple NS RRs are okay.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_->addRdata(generic::NS(ns_txt1));
+ ns_->addRdata(generic::NS(ns_txt2));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkSOA) {
+ // If the zone has no SOA it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 0 SOA records");
+ checkIssues();
+
+ // If null callback is specified, checkZone() only returns the final
+ // result.
+ ZoneCheckerCallbacks noerror_callbacks(
+ NULL, boost::bind(&ZoneCheckerTest::callback, this, _1, false));
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, noerror_callbacks));
+ checkIssues();
+
+ // If there are more than 1 SOA RR, it's also an error.
+ errors_.clear();
+ soa_->addRdata(generic::SOA(soa_txt));
+ soa_->addRdata(generic::SOA("ns2.example.com. . 0 0 0 0 0"));
+ rrsets_->addRRset(soa_);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has 2 SOA records");
+ checkIssues();
+
+ // If the SOA RRset is "empty", it's treated as an implementation
+ // (rather than operational) error and results in an exception.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ // Likewise, if the SOA RRset contains non SOA Rdata, it should be a bug.
+ rrsets_->removeRRset(zname_, zclass_, RRType::SOA());
+ soa_.reset(new RRset(zname_, zclass_, RRType::SOA(), RRTTL(60)));
+ soa_->addRdata(createRdata(RRType::NS(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(soa_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNS) {
+ // If the zone has no NS at origin it triggers an error.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: has no NS records");
+ checkIssues();
+
+ // Check two buggy cases like the SOA tests
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(createRdata(RRType::TXT(), zclass_, "ns.example.com"));
+ rrsets_->addRRset(ns_);
+ EXPECT_THROW(checkZone(zname_, zclass_, *rrsets_, callbacks_), Unexpected);
+ checkIssues(); // no error/warning should be reported
+}
+
+TEST_F(ZoneCheckerTest, checkNSData) {
+ const Name ns_name("ns.example.com");
+
+ // If a ("in-bailiwick") NS name doesn't have an address record, it's
+ // reported as a warning.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::A());
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // Same check, but disabling warning callback. Same result, but without
+ // the warning.
+ ZoneCheckerCallbacks nowarn_callbacks(
+ boost::bind(&ZoneCheckerTest::callback, this, _1, true), NULL);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, nowarn_callbacks));
+ checkIssues();
+
+ // A tricky case: if the name matches a wildcard, it should technically
+ // be considered valid, but this checker doesn't check that far and still
+ // warns.
+ RRsetPtr wild(new RRset(Name("*.example.com"), zclass_, RRType::A(),
+ RRTTL(0)));
+ wild->addRdata(in::A("192.0.2.255"));
+ rrsets_->addRRset(wild);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+
+ // If there's a CNAME at the name instead, it's an error.
+ rrsets_->removeRRset(Name("*.example.com"), zclass_, RRType::A());
+ RRsetPtr cname(new RRset(ns_name, zclass_, RRType::CNAME(), RRTTL(60)));
+ cname->addRdata(generic::CNAME("cname.example.com"));
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't have to be A. An AAAA is enough.
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ RRsetPtr aaaa(new RRset(ns_name, zclass_, RRType::AAAA(), RRTTL(60)));
+ aaaa->addRdata(in::AAAA("2001:db8::1"));
+ rrsets_->addRRset(aaaa);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Coexisting CNAME makes it error (CNAME with other record is itself
+ // invalid, but it's a different issue in this context)
+ rrsets_->addRRset(cname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.example.com' is "
+ "a CNAME (illegal per RFC2181)");
+ checkIssues();
+
+ // It doesn't matter if the NS name is "out of bailiwick".
+ rrsets_->removeRRset(ns_name, zclass_, RRType::CNAME());
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Note that if the NS name is the origin name, it should be checked
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(zname_));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDelegation) {
+ // Tests various cases where there's a zone cut due to delegation between
+ // the zone origin and the NS name. In each case the NS name doesn't have
+ // an address record.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to delegation in the middle; the check for the address
+ // record should be skipped.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr child_ns(new RRset(Name("child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut at the NS name. Same result.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(ns_name, zclass_, RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut below the NS name. The check applies.
+ rrsets_->removeRRset(child_ns->getName(), zclass_, RRType::NS());
+ child_ns.reset(new RRset(Name("another.ns.child.example.com"), zclass_,
+ RRType::NS(), RRTTL(60)));
+ child_ns->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(child_ns);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+TEST_F(ZoneCheckerTest, checkNSWithDNAME) {
+ // Similar to the above case, but the zone cut is due to DNAME. This is
+ // an invalid configuration.
+ const Name ns_name("ns.child.example.com");
+
+ // Zone cut due to DNAME at the zone origin. This is an invalid case.
+ rrsets_->removeRRset(zname_, zclass_, RRType::NS());
+ ns_.reset(new RRset(zname_, zclass_, RRType::NS(), RRTTL(60)));
+ ns_->addRdata(generic::NS(ns_name));
+ rrsets_->addRRset(ns_);
+ RRsetPtr dname(new RRset(zname_, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'example.com'");
+ checkIssues();
+
+ // Zone cut due to DNAME in the middle. Same result.
+ rrsets_->removeRRset(zname_, zclass_, RRType::DNAME());
+ dname.reset(new RRset(Name("child.example.com"), zclass_, RRType::DNAME(),
+ RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_FALSE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_errors_.push_back("zone example.com/IN: NS 'ns.child.example.com'"
+ " is below a DNAME 'child.example.com'");
+ checkIssues();
+
+ // A tricky case: there's also an NS at the name that has DNAME. It's
+ // prohibited per RFC6672 so we could say it's "undefined". Nevertheless,
+ // this implementation prefers the NS and skips further checks.
+ ns_.reset(new RRset(Name("child.example.com"), zclass_, RRType::NS(),
+ RRTTL(60)));
+ ns_->addRdata(generic::NS("ns.example.org"));
+ rrsets_->addRRset(ns_);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ checkIssues();
+
+ // Zone cut due to DNAME at the NS name. In this case DNAME doesn't
+ // affect the NS name, so it should result in "no address record" warning.
+ rrsets_->removeRRset(dname->getName(), zclass_, RRType::DNAME());
+ rrsets_->removeRRset(ns_->getName(), zclass_, RRType::NS());
+ dname.reset(new RRset(ns_name, zclass_, RRType::DNAME(), RRTTL(60)));
+ dname->addRdata(generic::DNAME("example.org"));
+ rrsets_->addRRset(dname);
+ EXPECT_TRUE(checkZone(zname_, zclass_, *rrsets_, callbacks_));
+ expected_warns_.push_back("zone example.com/IN: NS has no address");
+ checkIssues();
+}
+
+}
diff --git a/src/lib/dns/zone_checker.cc b/src/lib/dns/zone_checker.cc
new file mode 100644
index 0000000..aa307d2
--- /dev/null
+++ b/src/lib/dns/zone_checker.cc
@@ -0,0 +1,196 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <dns/zone_checker.h>
+
+#include <dns/name.h>
+#include <dns/rdataclass.h>
+#include <dns/rrclass.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrset_collection_base.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <string>
+
+using boost::lexical_cast;
+using std::string;
+
+namespace isc {
+namespace dns {
+
+namespace {
+std::string
+zoneText(const Name& zone_name, const RRClass& zone_class) {
+ return (zone_name.toText(true) + "/" + zone_class.toText());
+}
+
+void
+checkSOA(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callback) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::SOA());
+ size_t count = 0;
+ if (rrset) {
+ for (RdataIteratorPtr rit = rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next(), ++count) {
+ if (dynamic_cast<const rdata::generic::SOA*>(&rit->getCurrent()) ==
+ NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in SOA");
+ }
+ }
+ if (count == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty SOA RRset");
+ }
+ }
+ if (count != 1) {
+ callback.error("zone " + zoneText(zone_name, zone_class) + ": has " +
+ lexical_cast<string>(count) + " SOA records");
+ }
+}
+
+// Check if a target name is beyond zone cut, either due to delegation or
+// DNAME. Note that DNAME works on the origin but not on the name itself,
+// while delegation works on the name itself (but the NS at the origin is not
+// delegation).
+ConstRRsetPtr
+findZoneCut(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets, const Name& target_name) {
+ const unsigned int origin_count = zone_name.getLabelCount();
+ const unsigned int target_count = target_name.getLabelCount();
+ assert(origin_count <= target_count);
+
+ for (unsigned int l = origin_count; l <= target_count; ++l) {
+ const Name& mid_name = (l == target_count) ? target_name :
+ target_name.split(target_count - l);
+
+ ConstRRsetPtr found;
+ if (l != origin_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::NS())) !=
+ NULL) {
+ return (found);
+ }
+ if (l != target_count &&
+ (found = zone_rrsets.find(mid_name, zone_class, RRType::DNAME()))
+ != NULL) {
+ return (found);
+ }
+ }
+ return (ConstRRsetPtr());
+}
+
+// Check if each "in-zone" NS name has an address record, identifying some
+// error cases.
+void
+checkNSNames(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ConstRRsetPtr ns_rrset, ZoneCheckerCallbacks& callbacks) {
+ if (ns_rrset->getRdataCount() == 0) {
+ // this should be an implementation bug, not an operational error.
+ isc_throw(Unexpected, "Zone checker found an empty NS RRset");
+ }
+
+ for (RdataIteratorPtr rit = ns_rrset->getRdataIterator();
+ !rit->isLast();
+ rit->next()) {
+ const rdata::generic::NS* ns_data =
+ dynamic_cast<const rdata::generic::NS*>(&rit->getCurrent());
+ if (ns_data == NULL) {
+ isc_throw(Unexpected, "Zone checker found bad RDATA in NS");
+ }
+ const Name& ns_name = ns_data->getNSName();
+ const NameComparisonResult::NameRelation reln =
+ ns_name.compare(zone_name).getRelation();
+ if (reln != NameComparisonResult::EQUAL &&
+ reln != NameComparisonResult::SUBDOMAIN) {
+ continue; // not in the zone. we can ignore it.
+ }
+
+ // Check if there's a zone cut between the origin and the NS name.
+ ConstRRsetPtr cut_rrset = findZoneCut(zone_name, zone_class,
+ zone_rrsets, ns_name);
+ if (cut_rrset) {
+ if (cut_rrset->getType() == RRType::NS()) {
+ continue; // delegation; making the NS name "out of zone".
+ } else if (cut_rrset->getType() == RRType::DNAME()) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is " +
+ "below a DNAME '" +
+ cut_rrset->getName().toText(true) +
+ "' (illegal per RFC6672)");
+ continue;
+ } else {
+ assert(false);
+ }
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::CNAME()) != NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": NS '" + ns_name.toText(true) + "' is a CNAME " +
+ "(illegal per RFC2181)");
+ continue;
+ }
+ if (zone_rrsets.find(ns_name, zone_class, RRType::A()) == NULL &&
+ zone_rrsets.find(ns_name, zone_class, RRType::AAAA()) == NULL) {
+ callbacks.warn("zone " + zoneText(zone_name, zone_class) +
+ ": NS has no address records (A or AAAA)");
+ }
+ }
+}
+
+void
+checkNS(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ ZoneCheckerCallbacks& callbacks) {
+ ConstRRsetPtr rrset =
+ zone_rrsets.find(zone_name, zone_class, RRType::NS());
+ if (rrset == NULL) {
+ callbacks.error("zone " + zoneText(zone_name, zone_class) +
+ ": has no NS records");
+ return;
+ }
+ checkNSNames(zone_name, zone_class, zone_rrsets, rrset, callbacks);
+}
+
+// The following is a simple wrapper of checker callback so checkZone()
+// can also remember any critical errors.
+void
+errorWrapper(const string& reason, const ZoneCheckerCallbacks* callbacks,
+ bool* had_error) {
+ *had_error = true;
+ callbacks->error(reason);
+}
+}
+
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks) {
+ bool had_error = false;
+ ZoneCheckerCallbacks my_callbacks(
+ boost::bind(errorWrapper, _1, &callbacks, &had_error),
+ boost::bind(&ZoneCheckerCallbacks::warn, &callbacks, _1));
+
+ checkSOA(zone_name, zone_class, zone_rrsets, my_callbacks);
+ checkNS(zone_name, zone_class, zone_rrsets, my_callbacks);
+
+ return (!had_error);
+}
+
+} // end namespace dns
+} // end namespace isc
diff --git a/src/lib/dns/zone_checker.h b/src/lib/dns/zone_checker.h
new file mode 100644
index 0000000..dfb4946
--- /dev/null
+++ b/src/lib/dns/zone_checker.h
@@ -0,0 +1,162 @@
+// Copyright (C) 2012 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#ifndef ZONE_CHECKER_H
+#define ZONE_CHECKER_H 1
+
+#include <dns/dns_fwd.h>
+
+#include <boost/function.hpp>
+
+#include <string>
+
+namespace isc {
+namespace dns {
+
+/// \brief Set of callbacks used in zone checks.
+///
+/// Objects of this class are expected to be passed to \c checkZone().
+class ZoneCheckerCallbacks {
+public:
+ /// \brief Functor type of the callback on some issue (error or warning).
+ ///
+ /// Its parameter indicates the reason for the corresponding issue.
+ typedef boost::function<void(const std::string& reason)> IssueCallback;
+
+ /// \brief Constructor.
+ ///
+ /// Either or both of the callbacks can be empty, in which case the
+ /// corresponding callback will be effectively no-operation. This can be
+ /// used, for example, when the caller of \c checkZone() is only
+ /// interested in the final result. Note that a \c NULL pointer will be
+ /// implicitly converted to an empty functor object, so passing \c NULL
+ /// suffices.
+ ///
+ /// \throw none
+ ///
+ /// \param error_callback Callback functor to be called on critical errors.
+ /// \param warn_callback Callback functor to be called on non critical
+ /// issues.
+ ZoneCheckerCallbacks(const IssueCallback& error_callback,
+ const IssueCallback& warn_callback) :
+ error_callback_(error_callback), warn_callback_(warn_callback)
+ {}
+
+ /// \brief Call the callback for a critical error.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the error.
+ void error(const std::string& reason) const {
+ if (!error_callback_.empty()) {
+ error_callback_(reason);
+ }
+ }
+
+ /// \brief Call the callback for a non critical issue.
+ ///
+ /// This method itself is exception free, but propagates any exception
+ /// thrown from the callback.
+ ///
+ /// \param reason Textual representation of the reason for the issue.
+ void warn(const std::string& reason) const {
+ if (!warn_callback_.empty())
+ warn_callback_(reason);
+ }
+
+private:
+ IssueCallback error_callback_;
+ IssueCallback warn_callback_;
+};
+
+/// \brief Perform basic integrity checks on zone RRsets.
+///
+/// This function performs some lightweight checks on zone's SOA and (apex)
+/// NS records. Here, lightweight means it doesn't require traversing
+/// the entire zone, and should be expected to complete reasonably quickly
+/// regardless of the size of the zone.
+///
+/// It distinguishes "critical" errors and other undesirable issues:
+/// the former should be interpreted as the resulting zone shouldn't be used
+/// further, e.g, by an authoritative server implementation; the latter means
+/// the issues are better to be addressed but are not necessarily considered
+/// to make the zone invalid. Critical errors are reported via the
+/// \c error() method of \c callbacks, and non critical issues are reported
+/// via its \c warn() method.
+///
+/// Specific checks performed by this function is as follows. Failure of
+/// a check is considered a critical error unless noted otherwise:
+/// - There is exactly one SOA RR at the zone apex.
+/// - There is at least one NS RR at the zone apex.
+/// - For each apex NS record, if the NS name (the RDATA of the record) is
+/// in the zone (i.e., it's a subdomain of the zone origin and above any
+/// zone cut due to delegation), check the following:
+/// - the NS name should have an address record (AAAA or A). Failure of
+/// this check is considered a non critical issue.
+/// - the NS name does not have a CNAME. This is prohibited by Section
+/// 10.3 of RFC 2181.
+/// - the NS name is not subject to DNAME substitution. This is prohibited
+/// by Section 4 of RFC 6672.
+/// .
+///
+/// In addition, when the check is completed without any critical error, this
+/// function guarantees that RRsets for the SOA and (apex) NS stored in the
+/// passed RRset collection have the expected type of Rdata objects,
+/// i.e., generic::SOA and generic::NS, respectively. (This is normally
+/// expected to be the case, but not guaranteed by the API).
+///
+/// As for the check on the existence of AAAA or A records for NS names,
+/// it should be noted that BIND 9 treats this as a critical error.
+/// It's not clear whether it's an implementation dependent behavior or
+/// based on the protocol standard (it looks like the former), but to make
+/// it sure we need to confirm there is even no wildcard match for the names.
+/// This should be a very rare configuration, and more expensive to detect,
+/// so we do not check this condition, and treat this case as a non critical
+/// issue.
+///
+/// This function indicates the result of the checks (whether there is a
+/// critical error) via the return value: It returns \c true if there is no
+/// critical error and returns \c false otherwise. It doesn't throw an
+/// exception on encountering an error so that it can report as many errors
+/// as possible in a single call. If an exception is a better way to signal
+/// the error, the caller can pass a callback object that throws from its
+/// \c error() method.
+///
+/// This function can still throw an exception if it finds a really bogus
+/// condition that is most likely to be an implementation bug of the caller.
+/// Such cases include when an RRset contained in the RRset collection is
+/// empty.
+///
+/// \throw Unexpected Conditions that suggest a caller's bug (see the
+/// description)
+///
+/// \param zone_name The name of the zone to be checked
+/// \param zone_class The RR class of the zone to be checked
+/// \param zone_rrsets The collection of RRsets of the zone
+/// \param callbacks Callback object used to report errors and issues
+///
+/// \return \c true if no critical errors are found; \c false otherwise.
+bool
+checkZone(const Name& zone_name, const RRClass& zone_class,
+ const RRsetCollectionBase& zone_rrsets,
+ const ZoneCheckerCallbacks& callbacks);
+
+} // namespace dns
+} // namespace isc
+#endif // ZONE_CHECKER_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/log/log_messages.mes b/src/lib/log/log_messages.mes
index f150f39..4bc8c98 100644
--- a/src/lib/log/log_messages.mes
+++ b/src/lib/log/log_messages.mes
@@ -97,7 +97,7 @@ There may be several reasons why this message may appear:
- The message ID has been mis-spelled in the local message file.
- The program outputting the message may not use that particular message
-(e.g. it originates in a module not used by the program.)
+(e.g. it originates in a module not used by the program).
- The local file was written for an earlier version of the BIND 10 software
and the later version no longer generates that message.
@@ -131,7 +131,7 @@ version of BIND 10.
% LOG_READING_LOCAL_FILE reading local message file %1
This is an informational message output by BIND 10 when it starts to read
a local message file. (A local message file may replace the text of
-one of more messages; the ID of the message will not be changed though.)
+one or more messages; the ID of the message will not be changed though.)
% LOG_READ_ERROR error reading from message file %1: %2
The specified error was encountered reading from the named message file.
diff --git a/src/lib/log/logger_manager_impl.cc b/src/lib/log/logger_manager_impl.cc
index 6862d0c..711eae9 100644
--- a/src/lib/log/logger_manager_impl.cc
+++ b/src/lib/log/logger_manager_impl.cc
@@ -12,6 +12,8 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
+#include <config.h>
+
#include <algorithm>
#include <iostream>
@@ -21,6 +23,7 @@
#include <log4cplus/consoleappender.h>
#include <log4cplus/fileappender.h>
#include <log4cplus/syslogappender.h>
+#include <log4cplus/helpers/loglog.h>
#include <log/logger.h>
#include <log/logger_support.h>
@@ -196,6 +199,13 @@ void LoggerManagerImpl::initRootLogger(isc::log::Severity severity,
{
log4cplus::Logger::getDefaultHierarchy().resetConfiguration();
+ // Disable log4cplus' own logging, unless --enable-debug was
+ // specified to configure. Note that this does not change
+ // LogLog's levels (that is still just INFO).
+#ifndef ENABLE_DEBUG
+ log4cplus::helpers::LogLog::getLogLog()->setQuietMode(true);
+#endif
+
// Set the log4cplus root to not output anything - effectively we are
// ignoring it.
log4cplus::Logger::getRoot().setLogLevel(log4cplus::OFF_LOG_LEVEL);
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index e072de8..8f5f144 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,5 +1,5 @@
SUBDIRS = datasrc cc config dns log net notify util testutils acl bind10
-SUBDIRS += xfrin log_messages server_common ddns sysinfo
+SUBDIRS += xfrin log_messages server_common ddns sysinfo statistics
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/bind10/component.py b/src/lib/python/isc/bind10/component.py
index 1f7006c..febeb10 100644
--- a/src/lib/python/isc/bind10/component.py
+++ b/src/lib/python/isc/bind10/component.py
@@ -177,8 +177,14 @@ class BaseComponent:
self._start_internal()
except Exception as e:
logger.error(BIND10_COMPONENT_START_EXCEPTION, self.name(), e)
- self.failed(None)
- raise
+ try:
+ self.failed(None)
+ finally:
+ # Even failed() can fail if this happens during initial startup
+ # time. In that case we'd rather propagate the original reason
+ # for the failure than the fact that failed() failed. So we
+ # always re-raise the original exception.
+ raise e
def stop(self):
"""
diff --git a/src/lib/python/isc/bind10/special_component.py b/src/lib/python/isc/bind10/special_component.py
index 688ccf5..dcd9b64 100644
--- a/src/lib/python/isc/bind10/special_component.py
+++ b/src/lib/python/isc/bind10/special_component.py
@@ -17,11 +17,7 @@ from isc.bind10.component import Component, BaseComponent
import isc.bind10.sockcreator
from bind10_config import LIBEXECPATH
import os
-import posix
import isc.log
-from isc.log_messages.bind10_messages import *
-
-logger = isc.log.Logger("boss")
class SockCreator(BaseComponent):
"""
@@ -36,8 +32,6 @@ class SockCreator(BaseComponent):
def __init__(self, process, boss, kind, address=None, params=None):
BaseComponent.__init__(self, boss, kind)
self.__creator = None
- self.__uid = boss.uid
- self.__gid = boss.gid
def _start_internal(self):
self._boss.curproc = 'b10-sockcreator'
@@ -46,12 +40,9 @@ class SockCreator(BaseComponent):
self._boss.register_process(self.pid(), self)
self._boss.set_creator(self.__creator)
self._boss.log_started(self.pid())
- if self.__gid is not None:
- logger.info(BIND10_SETGID, self.__gid)
- posix.setgid(self.__gid)
- if self.__uid is not None:
- logger.info(BIND10_SETUID, self.__uid)
- posix.setuid(self.__uid)
+
+ # We are now ready for switching user.
+ self._boss.change_user()
def _stop_internal(self):
self.__creator.terminate()
diff --git a/src/lib/python/isc/bind10/tests/component_test.py b/src/lib/python/isc/bind10/tests/component_test.py
index 18efea7..8603201 100644
--- a/src/lib/python/isc/bind10/tests/component_test.py
+++ b/src/lib/python/isc/bind10/tests/component_test.py
@@ -104,10 +104,10 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.__stop_process_params = None
self.__start_simple_params = None
# Pretending to be boss
- self.gid = None
- self.__gid_set = None
- self.uid = None
- self.__uid_set = None
+ self.__change_user_called = False
+
+ def change_user(self):
+ self.__change_user_called = True # just record the fact it's called
def __start(self):
"""
@@ -624,12 +624,6 @@ class ComponentTests(BossUtils, unittest.TestCase):
self.assertTrue(process.killed)
self.assertFalse(process.terminated)
- def setgid(self, gid):
- self.__gid_set = gid
-
- def setuid(self, uid):
- self.__uid_set = uid
-
class FakeCreator:
def pid(self):
return 42
@@ -655,35 +649,19 @@ class ComponentTests(BossUtils, unittest.TestCase):
"""
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- orig_setgid = isc.bind10.special_component.posix.setgid
- orig_setuid = isc.bind10.special_component.posix.setuid
- isc.bind10.special_component.posix.setgid = self.setgid
- isc.bind10.special_component.posix.setuid = self.setuid
orig_creator = \
isc.bind10.special_component.isc.bind10.sockcreator.Creator
# Just ignore the creator call
isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
lambda path: self.FakeCreator()
component.start()
- # No gid/uid set in boss, nothing called.
- self.assertIsNone(self.__gid_set)
- self.assertIsNone(self.__uid_set)
+ self.assertTrue(self.__change_user_called)
# Doesn't do anything, but doesn't crash
component.stop()
component.kill()
component.kill(True)
- self.gid = 4200
- self.uid = 42
component = isc.bind10.special_component.SockCreator(None, self,
'needed', None)
- component.start()
- # This time, it get's called
- self.assertEqual(4200, self.__gid_set)
- self.assertEqual(42, self.__uid_set)
- isc.bind10.special_component.posix.setgid = orig_setgid
- isc.bind10.special_component.posix.setuid = orig_setuid
- isc.bind10.special_component.isc.bind10.sockcreator.Creator = \
- orig_creator
class TestComponent(BaseComponent):
"""
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index a200dfc..9563cab 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -234,6 +234,7 @@ class ConfigManager:
def notify_boss(self):
"""Notifies the Boss module that the Config Manager is running"""
+ # TODO: Use a real, broadcast notification here.
self.cc.group_sendmsg({"running": "ConfigManager"}, "Boss")
def set_module_spec(self, spec):
diff --git a/src/lib/python/isc/datasrc/client_inc.cc b/src/lib/python/isc/datasrc/client_inc.cc
index 83bf0ed..9e66016 100644
--- a/src/lib/python/isc/datasrc/client_inc.cc
+++ b/src/lib/python/isc/datasrc/client_inc.cc
@@ -100,15 +100,12 @@ 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\
+ creating the zone, or there are some really unexpected\n\
+ errors.\n\
\n\
Parameters:\n\
name The (fully qualified) name of the zone to create\n\
@@ -117,6 +114,42 @@ Return Value(s): True if the zone was added, False if it already\n\
existed\n\
";
+const char* const DataSourceClient_deleteZone_doc = "\
+delete_zone(zone_name) -> bool\n\
+\n\
+Delete a zone from the data source.\n\
+\n\
+This method also checks if the specified zone exists in the data\n\
+source, and returns true/false depending on whether the zone\n\
+existed/not existed, respectively. In either case, on successful\n\
+return it ensures the data source does not contain the specified name\n\
+of the zone.\n\
+\n\
+Note: This is a tentative API, and this method is likely to change or\n\
+be removed in the near future. For that reason, it currently provides\n\
+a default implementation that throws NotImplemented. Note also that\n\
+this method does not delete other database records related to the\n\
+zone, such as zone's resource records or differences corresponding to\n\
+updates made in the zone. This is primarily for implementation\n\
+simplicity (in the currently intended usage there wouldn't be such\n\
+other data at the time of this call anyway) and due to the fact that\n\
+details of managing zones is still in flux. Once the design in this\n\
+area is fixed we may revisit the behavior.\n\
+\n\
+Exceptions:\n\
+ NotImplemented If the datasource backend does not support direct\n\
+ zone deletion.\n\
+ DataSourceError If something goes wrong in the data source while\n\
+ deleting the zone, or there are some really unexpected\n\
+ errors.\n\
+\n\
+Parameters:\n\
+ zone_name The (fully qualified) name of the zone to be deleted\n\
+\n\
+Return Value(s): true if the zone previously existed and has been\n\
+deleted by this method; false if the zone didn't exist.\n\
+";
+
const char* const DataSourceClient_getIterator_doc = "\
get_iterator(name, separate_rrs=False) -> ZoneIterator\n\
\n\
diff --git a/src/lib/python/isc/datasrc/client_python.cc b/src/lib/python/isc/datasrc/client_python.cc
index f360445..9e4bd42 100644
--- a/src/lib/python/isc/datasrc/client_python.cc
+++ b/src/lib/python/isc/datasrc/client_python.cc
@@ -123,6 +123,36 @@ DataSourceClient_createZone(PyObject* po_self, PyObject* args) {
}
PyObject*
+DataSourceClient_deleteZone(PyObject* po_self, PyObject* args) {
+ s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
+ PyObject* po_name;
+ if (PyArg_ParseTuple(args, "O!", &name_type, &po_name)) {
+ try {
+ if (self->client->deleteZone(PyName_ToName(po_name))) {
+ Py_RETURN_TRUE;
+ } else {
+ Py_RETURN_FALSE;
+ }
+ } catch (const DataSourceError& dse) {
+ PyErr_SetString(getDataSourceException("Error"), dse.what());
+ return (NULL);
+ } catch (const isc::NotImplemented& ni) {
+ PyErr_SetString(getDataSourceException("NotImplemented"),
+ ni.what());
+ return (NULL);
+ } catch (const std::exception& exc) {
+ PyErr_SetString(getDataSourceException("Error"), exc.what());
+ return (NULL);
+ } catch (...) {
+ PyErr_SetString(getDataSourceException("Error"),
+ "Unexpected exception");
+ return (NULL);
+ }
+ }
+ return (NULL);
+}
+
+PyObject*
DataSourceClient_getIterator(PyObject* po_self, PyObject* args) {
s_DataSourceClient* const self = static_cast<s_DataSourceClient*>(po_self);
PyObject* name_obj;
@@ -263,6 +293,8 @@ PyMethodDef DataSourceClient_methods[] = {
DataSourceClient_findZone_doc },
{ "create_zone", DataSourceClient_createZone, METH_VARARGS,
DataSourceClient_createZone_doc },
+ { "delete_zone", DataSourceClient_deleteZone, METH_VARARGS,
+ DataSourceClient_deleteZone_doc },
{ "get_iterator",
DataSourceClient_getIterator, METH_VARARGS,
DataSourceClient_getIterator_doc },
diff --git a/src/lib/python/isc/datasrc/datasrc.cc b/src/lib/python/isc/datasrc/datasrc.cc
index 37c9baa..c183af9 100644
--- a/src/lib/python/isc/datasrc/datasrc.cc
+++ b/src/lib/python/isc/datasrc/datasrc.cc
@@ -21,6 +21,7 @@
#include <datasrc/client.h>
#include <datasrc/database.h>
#include <datasrc/sqlite3_accessor.h>
+#include <datasrc/zone_loader.h>
#include "datasrc.h"
#include "client_python.h"
@@ -34,6 +35,9 @@
#include <util/python/pycppwrapper_util.h>
#include <dns/python/pydnspp_common.h>
+#include <stdexcept>
+#include <string>
+
using namespace isc::datasrc;
using namespace isc::datasrc::python;
using namespace isc::util::python;
@@ -195,22 +199,20 @@ initModulePart_ZoneLoader(PyObject* mod) {
}
Py_INCREF(&zone_loader_type);
- return (true);
-}
-
-bool
-initModulePart_ZoneUpdater(PyObject* mod) {
- // We initialize the static description object with PyType_Ready(),
- // then add it to the module. This is not just a check! (leaving
- // this out results in segmentation faults)
- if (PyType_Ready(&zoneupdater_type) < 0) {
+ try {
+ installClassVariable(zone_loader_type, "PROGRESS_UNKNOWN",
+ Py_BuildValue("d", ZoneLoader::PROGRESS_UNKNOWN));
+ } catch (const std::exception& ex) {
+ const std::string ex_what =
+ "Unexpected failure in ZoneLoader initialization: " +
+ std::string(ex.what());
+ PyErr_SetString(po_IscException, ex_what.c_str());
return (false);
- }
- void* zip = &zoneupdater_type;
- if (PyModule_AddObject(mod, "ZoneUpdater", static_cast<PyObject*>(zip)) < 0) {
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneLoader initialization");
return (false);
}
- Py_INCREF(&zoneupdater_type);
return (true);
}
diff --git a/src/lib/python/isc/datasrc/tests/.gitignore b/src/lib/python/isc/datasrc/tests/.gitignore
index 58ea8cd..0d94a86 100644
--- a/src/lib/python/isc/datasrc/tests/.gitignore
+++ b/src/lib/python/isc/datasrc/tests/.gitignore
@@ -1 +1,2 @@
/*.sqlite3.copied
+/zoneloadertest.sqlite3
diff --git a/src/lib/python/isc/datasrc/tests/datasrc_test.py b/src/lib/python/isc/datasrc/tests/datasrc_test.py
index fef04a4..36cf951 100644
--- a/src/lib/python/isc/datasrc/tests/datasrc_test.py
+++ b/src/lib/python/isc/datasrc/tests/datasrc_test.py
@@ -528,6 +528,41 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
rrset.to_text())
+ def test_updater_rrset_collection(self):
+ dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
+ updater = dsc.get_updater(isc.dns.Name("example.com"), False)
+ updater_refs = sys.getrefcount(updater)
+
+ # Get (internally create) updater's RRset collection
+ rrsets = updater.get_rrset_collection()
+
+ # From this point we cannot make further updates
+ rrset = RRset(isc.dns.Name('www.example.com'), isc.dns.RRClass.IN(),
+ isc.dns.RRType.AAAA(), isc.dns.RRTTL(10))
+ rrset.add_rdata(isc.dns.Rdata(isc.dns.RRType.AAAA(),
+ isc.dns.RRClass.IN(), '2001:db8::1'))
+ self.assertRaises(isc.datasrc.Error, updater.add_rrset, rrset)
+
+ # Checks basic API
+ found = rrsets.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A())
+ self.assertEqual("www.example.com. 3600 IN A 192.0.2.1\n",
+ found.to_text())
+ self.assertEqual(None, rrsets.find(isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(),
+ isc.dns.RRType.AAAA()))
+
+ # Once committed collection cannot be used any more.
+ updater.commit()
+ self.assertRaises(isc.dns.RRsetCollectionError,
+ rrsets.find, isc.dns.Name("www.example.com"),
+ isc.dns.RRClass.IN(), isc.dns.RRType.A())
+
+ # When we destroy the RRsetCollection it should release the refcount
+ # to the updater.
+ rrsets = None
+ self.assertEqual(updater_refs, sys.getrefcount(updater))
+
def test_two_modules(self):
# load two modules, and check if they don't interfere
mem_cfg = { "type": "memory", "class": "IN", "zones": [] };
@@ -634,23 +669,27 @@ class DataSrcUpdater(unittest.TestCase):
self.assertEqual(None, iterator.get_soa())
self.assertEqual(None, iterator.get_next_rrset())
- def test_create_zone_args(self):
+ def test_create_or_delete_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)
+ for method in [dsc.create_zone, dsc.delete_zone]:
+ self.assertRaises(TypeError, method)
+ self.assertRaises(TypeError, method, 1)
+ self.assertRaises(TypeError, method, None)
+ self.assertRaises(TypeError, method, "foo.")
+ self.assertRaises(TypeError, method, isc.dns.Name("example.org"),
+ 1)
- def test_create_zone(self):
+ def test_create_delete_zone(self):
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
# Note, using example.org here, which should not exist
zone_name = isc.dns.Name("example.org")
self.assertIsNone(dsc.get_updater(zone_name, True))
self.assertRaises(isc.datasrc.Error, dsc.get_iterator, zone_name)
+ # delete_zone should return False, meaning it didn't exist.
+ self.assertFalse(dsc.delete_zone(zone_name))
+
self.assertTrue(dsc.create_zone(zone_name))
# should exist now, we should be able to get an updater
@@ -663,6 +702,12 @@ class DataSrcUpdater(unittest.TestCase):
# Trying to create it again should return False
self.assertFalse(dsc.create_zone(zone_name))
+ # Now that it exists, delete_zone should return True, and it cannot
+ # be found any more.
+ self.assertTrue(dsc.delete_zone(zone_name))
+ self.assertEqual(isc.datasrc.DataSourceClient.NOTFOUND,
+ dsc.find_zone(zone_name)[0])
+
def test_create_zone_locked(self):
zone_name = isc.dns.Name("example.org")
dsc = isc.datasrc.DataSourceClient("sqlite3", WRITE_ZONE_DB_CONFIG)
@@ -670,10 +715,13 @@ class DataSrcUpdater(unittest.TestCase):
# Should fail since db is locked
self.assertRaises(isc.datasrc.Error, dsc.create_zone, zone_name)
+ self.assertRaises(isc.datasrc.Error, dsc.delete_zone,
+ isc.dns.Name('example.com'))
- # Cancel updater, then create should succeed
+ # Cancel updater, then create/delete should succeed
updater = None
self.assertTrue(dsc.create_zone(zone_name))
+ self.assertTrue(dsc.delete_zone(zone_name))
def test_create_zone_not_implemented(self):
mem_cfg = '{ "type": "memory", "class": "IN", "zones": [] }';
diff --git a/src/lib/python/isc/datasrc/tests/zone_loader_test.py b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
index cb239ed..62f67cd 100644
--- a/src/lib/python/isc/datasrc/tests/zone_loader_test.py
+++ b/src/lib/python/isc/datasrc/tests/zone_loader_test.py
@@ -38,7 +38,7 @@ ORIG_SOA_TXT = 'example.com. 3600 IN SOA master.example.com. ' +\
'admin.example.com. 1234 3600 1800 2419200 7200\n'
NEW_SOA_TXT = 'example.com. 1000 IN SOA a.dns.example.com. ' +\
'mail.example.com. 1 1 1 1 1\n'
-
+PROGRESS_UNKNOWN = isc.datasrc.ZoneLoader.PROGRESS_UNKNOWN
class ZoneLoaderTests(unittest.TestCase):
def setUp(self):
@@ -111,22 +111,50 @@ class ZoneLoaderTests(unittest.TestCase):
def test_load_from_file(self):
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.test_file)
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(0, self.loader.get_progress())
+
self.check_load()
+ # Expected values are hardcoded, taken from the test zone file,
+ # assuming it won't change too often. progress should reach 100% (=1).
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(1, self.loader.get_progress())
+
def test_load_from_client(self):
self.source_client = isc.datasrc.DataSourceClient('sqlite3',
DB_SOURCE_CLIENT_CONFIG)
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.source_client)
+
+ self.assertEqual(0, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
self.check_load()
- def check_load_incremental(self):
+ # In case of loading from another data source, progress is unknown.
+ self.assertEqual(8, self.loader.get_rr_count())
+ self.assertEqual(PROGRESS_UNKNOWN, self.loader.get_progress())
+
+ def check_load_incremental(self, from_file=True):
# New zone has 8 RRs
# After 5, it should return False
self.assertFalse(self.loader.load_incremental(5))
# New zone should not have been loaded yet
self.check_zone_soa(ORIG_SOA_TXT)
+ # In case it's from a zone file, get_progress should be in the middle
+ # of (0, 1). expected value is taken from the test zone file
+ # (total size = 422, current position = 288)
+ if from_file:
+ # To avoid any false positive due to rounding errors, we convert
+ # them to near integers between 0 and 100.
+ self.assertEqual(int((288 * 100) / 422),
+ int(self.loader.get_progress() * 100))
+ # Also check the return value has higher precision.
+ self.assertNotEqual(int(288 * 100 / 422),
+ 100 * self.loader.get_progress())
+
# After 5 more, it should return True (only having read 3)
self.assertTrue(self.loader.load_incremental(5))
# New zone should now be loaded
@@ -147,7 +175,7 @@ class ZoneLoaderTests(unittest.TestCase):
DB_SOURCE_CLIENT_CONFIG)
self.loader = isc.datasrc.ZoneLoader(self.client, self.test_name,
self.source_client)
- self.check_load_incremental()
+ self.check_load_incremental(False)
def test_bad_file(self):
self.check_zone_soa(ORIG_SOA_TXT)
diff --git a/src/lib/python/isc/datasrc/updater_inc.cc b/src/lib/python/isc/datasrc/updater_inc.cc
index 32715ec..f040f85 100644
--- a/src/lib/python/isc/datasrc/updater_inc.cc
+++ b/src/lib/python/isc/datasrc/updater_inc.cc
@@ -76,6 +76,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
Todo As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
@@ -133,6 +137,10 @@ This method must not be called once commit() is performed. If it calls\n\
after commit() the implementation must throw a isc.datasrc.Error\n\
exception.\n\
\n\
+Implementations of ZoneUpdater may not allow adding or deleting RRsets\n\
+after get_rrset_collection() is called. In this case, implementations\n\
+throw an InvalidOperation exception.\n\
+\n\
Todo: As noted above we may have to revisit the design details as we\n\
gain experiences:\n\
\n\
@@ -178,4 +186,32 @@ Exceptions:\n\
error, or wrapper error\n\\n\
\n\
";
+
+// Modifications
+// - isc.datasrc.RRsetCollectionBase => isc.dns.RRsetCollectionBase
+// (in the Python wrapper, the former is completely invisible)
+// - remove other reference to isc.datasrc.RRsetCollectionBase
+const char* const ZoneUpdater_getRRsetCollection_doc = "\
+get_rrset_collection() -> isc.dns.RRsetCollectionBase \n\
+\n\
+Return an RRsetCollection for the updater.\n\
+\n\
+This method returns an RRsetCollection for the updater, implementing\n\
+the isc.dns.RRsetCollectionBase interface. Typically, the returned\n\
+RRsetCollection is a singleton for its ZoneUpdater. The returned\n\
+RRsetCollection object must not be used after its corresponding\n\
+ZoneUpdater has been destroyed. The returned RRsetCollection object\n\
+may be used to search RRsets from the ZoneUpdater. The actual\n\
+RRsetCollection returned has a behavior dependent on the ZoneUpdater\n\
+implementation.\n\
+\n\
+The behavior of the RRsetCollection is similar to the behavior of the\n\
+Zonefinder returned by get_finder(). Implementations of ZoneUpdater\n\
+may not allow adding or deleting RRsets after get_rrset_collection()\n\
+is called. Implementations of ZoneUpdater may disable a previously\n\
+returned RRsetCollection after commit() is called. If an\n\
+RRsetCollection is disabled, using methods such as find() and using\n\
+its iterator would cause an exception to be thrown.\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/updater_python.cc b/src/lib/python/isc/datasrc/updater_python.cc
index 97ffa00..cb727c3 100644
--- a/src/lib/python/isc/datasrc/updater_python.cc
+++ b/src/lib/python/isc/datasrc/updater_python.cc
@@ -32,6 +32,7 @@
#include <dns/python/rrset_python.h>
#include <dns/python/rrclass_python.h>
#include <dns/python/rrtype_python.h>
+#include <dns/python/rrset_collection_python.h>
#include "datasrc.h"
#include "updater_python.h"
@@ -195,6 +196,107 @@ ZoneUpdater_find_all(PyObject* po_self, PyObject* args) {
&self->cppobj->getFinder(), args));
}
+namespace {
+// Below we define Python RRsetCollection class generated by the updater.
+// It's never expected to be instantiated directly from Python code, so
+// everything is hidden here, and tp_init always fails.
+
+class s_UpdaterRRsetCollection : public s_RRsetCollection {
+public:
+ s_UpdaterRRsetCollection() : s_RRsetCollection() {}
+ PyObject* base_obj_;
+};
+
+int
+RRsetCollection_init(PyObject*, PyObject*, PyObject*) {
+ // can't be called directly; actually, the constructor shouldn't even be
+ // called, but we catch the case just in case.
+ PyErr_SetString(PyExc_TypeError,
+ "datasrc.RRsetCollection cannot be constructed directly");
+
+ return (-1);
+}
+
+void
+RRsetCollection_destroy(PyObject* po_self) {
+ s_UpdaterRRsetCollection* const self =
+ static_cast<s_UpdaterRRsetCollection*>(po_self);
+
+ // We don't own the C++ collection object; we shouldn't delete it here.
+
+ // Note: we need to check if this is NULL; it remains NULL in case of
+ // direct instantiation (which fails).
+ if (self->base_obj_ != NULL) {
+ Py_DECREF(self->base_obj_);
+ }
+ Py_TYPE(self)->tp_free(self);
+}
+
+PyTypeObject updater_rrset_collection_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "datasrc.UpdaterRRsetCollection",
+ sizeof(s_UpdaterRRsetCollection), // tp_basicsize
+ 0, // tp_itemsize
+ RRsetCollection_destroy, // tp_dealloc
+ NULL, // tp_print
+ NULL, // tp_getattr
+ NULL, // tp_setattr
+ NULL, // tp_reserved
+ NULL, // tp_repr
+ NULL, // tp_as_number
+ NULL, // tp_as_sequence
+ NULL, // tp_as_mapping
+ NULL, // tp_hash
+ NULL, // tp_call
+ NULL, // tp_str
+ NULL, // tp_getattro
+ NULL, // tp_setattro
+ NULL, // tp_as_buffer
+ Py_TPFLAGS_DEFAULT, // tp_flags
+ NULL,
+ NULL, // tp_traverse
+ NULL, // tp_clear
+ NULL, // tp_richcompare
+ 0, // tp_weaklistoffset
+ NULL, // tp_iter
+ NULL, // tp_iternext
+ NULL, // tp_methods
+ NULL, // tp_members
+ NULL, // tp_getset
+ NULL, // tp_base (rrset_collection_base_type, set at run time)
+ NULL, // tp_dict
+ NULL, // tp_descr_get
+ NULL, // tp_descr_set
+ 0, // tp_dictoffset
+ RRsetCollection_init, // tp_init
+ NULL, // tp_alloc
+ PyType_GenericNew, // tp_new
+ NULL, // tp_free
+ NULL, // tp_is_gc
+ NULL, // tp_bases
+ NULL, // tp_mro
+ NULL, // tp_cache
+ NULL, // tp_subclasses
+ NULL, // tp_weaklist
+ NULL, // tp_del
+ 0 // tp_version_tag
+};
+} // unnamed namespace
+
+PyObject*
+ZoneUpdater_getRRsetCollection(PyObject* po_self, PyObject*) {
+ s_ZoneUpdater* const self = static_cast<s_ZoneUpdater*>(po_self);
+
+ s_UpdaterRRsetCollection* collection =
+ static_cast<s_UpdaterRRsetCollection*>(
+ PyObject_New(s_RRsetCollection, &updater_rrset_collection_type));
+ collection->cppobj = &self->cppobj->getRRsetCollection();
+ collection->base_obj_ = po_self;;
+ Py_INCREF(collection->base_obj_);
+
+ return (collection);
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -207,6 +309,8 @@ PyMethodDef ZoneUpdater_methods[] = {
{ "delete_rrset", ZoneUpdater_deleteRRset,
METH_VARARGS, ZoneUpdater_deleteRRset_doc },
{ "commit", ZoneUpdater_commit, METH_NOARGS, ZoneUpdater_commit_doc },
+ { "get_rrset_collection", ZoneUpdater_getRRsetCollection,
+ METH_NOARGS, ZoneUpdater_getRRsetCollection_doc },
// Instead of a getFinder, we implement the finder functionality directly
// This is because ZoneFinder is non-copyable, and we should not create
// a ZoneFinder object from a reference only (which is what is returned
@@ -292,6 +396,56 @@ createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
return (py_zu);
}
+bool
+initModulePart_ZoneUpdater(PyObject* mod) {
+ // We initialize the static description object with PyType_Ready(),
+ // then add it to the module. This is not just a check! (leaving
+ // this out results in segmentation faults)
+ if (PyType_Ready(&zoneupdater_type) < 0) {
+ return (false);
+ }
+ void* zip = &zoneupdater_type;
+ if (PyModule_AddObject(mod, "ZoneUpdater",
+ static_cast<PyObject*>(zip)) < 0)
+ {
+ return (false);
+ }
+ Py_INCREF(&zoneupdater_type);
+
+ // get_rrset_collection() needs the base class type information. Since
+ // it's defined in a separate loadable module, we retrieve its C object
+ // via the Python interpreter. Directly referring to
+ // isc::dns::python::rrset_collection_base_type might work depending on
+ // the runtime environment (and in fact it does for some), but that would
+ // be less portable.
+ try {
+ if (updater_rrset_collection_type.tp_base == NULL) {
+ PyObjectContainer dns_module(PyImport_ImportModule("isc.dns"));
+ PyObjectContainer dns_dict(PyModule_GetDict(dns_module.get()));
+ // GetDict doesn't acquire a reference, so we need to get it to
+ // meet the container's requirement.
+ Py_INCREF(dns_dict.get());
+ PyObjectContainer base(
+ PyDict_GetItemString(dns_dict.get(), "RRsetCollectionBase"));
+ PyTypeObject* pt_rrset_collection_base =
+ static_cast<PyTypeObject*>(static_cast<void*>(base.get()));
+ updater_rrset_collection_type.tp_base = pt_rrset_collection_base;
+ if (PyType_Ready(&updater_rrset_collection_type) < 0) {
+ isc_throw(Unexpected, "failed to import isc.dns module");
+ }
+
+ // Make sure the base type won't suddenly disappear. Note that we
+ // are effectively leaking it; it's intentional.
+ Py_INCREF(base.get());
+ }
+ } catch (...) {
+ PyErr_SetString(PyExc_SystemError,
+ "Unexpected failure in ZoneUpdater initialization");
+ return (false);
+ }
+
+ return (true);
+}
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/updater_python.h b/src/lib/python/isc/datasrc/updater_python.h
index b09c524..b7f1452 100644
--- a/src/lib/python/isc/datasrc/updater_python.h
+++ b/src/lib/python/isc/datasrc/updater_python.h
@@ -36,7 +36,7 @@ extern PyTypeObject zoneupdater_type;
PyObject* createZoneUpdaterObject(isc::datasrc::ZoneUpdaterPtr source,
PyObject* base_obj = NULL);
-
+bool initModulePart_ZoneUpdater(PyObject* mod);
} // namespace python
} // namespace datasrc
} // namespace isc
diff --git a/src/lib/python/isc/datasrc/zone_loader_inc.cc b/src/lib/python/isc/datasrc/zone_loader_inc.cc
index 405ad1a..ae6fa3c 100644
--- a/src/lib/python/isc/datasrc/zone_loader_inc.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_inc.cc
@@ -113,4 +113,64 @@ on the next call (which will load 0 RRs). This is because the end of\n\
iterator or master file is detected when reading past the end, not\n\
when the last one is read.\n\
";
+
+const char* const ZoneLoader_getRRCount_doc = "\
+get_rr_count() -> integer\n\
+\n\
+Return the number of RRs loaded.\n\
+\n\
+This method returns the number of RRs loaded via this loader by the\n\
+time of the call. Before starting the load it will return 0. It will\n\
+return the total number of RRs of the zone on and after completing the\n\
+load.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
+
+// Modifications
+// - double => float
+const char* const ZoneLoader_getProgress_doc = "\
+get_progress() -> float\n\
+\n\
+Return the current progress of the loader.\n\
+\n\
+This method returns the current estimated progress of loader as a\n\
+value between 0 and 1 (inclusive); it's 0 before starting the load,\n\
+and 1 at the completion, and a value between these (exclusive) in the\n\
+middle of loading. It's an implementation detail how to calculate the\n\
+progress, which may vary depending on how the loader is constructed\n\
+and may even be impossible to detect effectively.\n\
+\n\
+If the progress cannot be determined, this method returns a special\n\
+value of PROGRESS_UNKNOWN, which is not included in the range between\n\
+0 and 1.\n\
+\n\
+As such, the application should use the return value only for\n\
+informational purposes such as logging. For example, it shouldn't be\n\
+used to determine whether loading is completed by comparing it to 1.\n\
+It should also expect the possibility of getting PROGRESS_UNKNOWN at\n\
+any call to this method; it shouldn't assume the specific way of\n\
+internal implementation as described below (which is provided for\n\
+informational purposes only).\n\
+\n\
+In this implementation, if the loader is constructed with a file name,\n\
+the progress value is measured by the number of characters read from\n\
+the zone file divided by the size of the zone file (with taking into\n\
+account any included files). Note that due to the possibility of\n\
+intermediate included files, the total file size cannot be fully fixed\n\
+until the completion of the load. And, due to this possibility, return\n\
+values from this method may not always increase monotonically.\n\
+\n\
+If it's constructed with another data source client, this method\n\
+always returns PROGRESS_UNKNOWN; in future, however, it may become\n\
+possible to return something more useful, e.g, based on the result of\n\
+get_rr_count() and the total number of RRs if the underlying data\n\
+source can provide the latter value efficiently.\n\
+\n\
+Exceptions:\n\
+ None\n\
+\n\
+";
} // unnamed namespace
diff --git a/src/lib/python/isc/datasrc/zone_loader_python.cc b/src/lib/python/isc/datasrc/zone_loader_python.cc
index 98264b3..c1915cc 100644
--- a/src/lib/python/isc/datasrc/zone_loader_python.cc
+++ b/src/lib/python/isc/datasrc/zone_loader_python.cc
@@ -187,6 +187,18 @@ ZoneLoader_loadIncremental(PyObject* po_self, PyObject* args) {
}
}
+PyObject*
+ZoneLoader_getRRCount(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("I", self->cppobj->getRRCount()));
+}
+
+PyObject*
+ZoneLoader_getProgress(PyObject* po_self, PyObject*) {
+ s_ZoneLoader* self = static_cast<s_ZoneLoader*>(po_self);
+ return (Py_BuildValue("d", self->cppobj->getProgress()));
+}
+
// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
@@ -197,6 +209,10 @@ PyMethodDef ZoneLoader_methods[] = {
{ "load", ZoneLoader_load, METH_NOARGS, ZoneLoader_load_doc },
{ "load_incremental", ZoneLoader_loadIncremental, METH_VARARGS,
ZoneLoader_loadIncremental_doc },
+ { "get_rr_count", ZoneLoader_getRRCount, METH_NOARGS,
+ ZoneLoader_getRRCount_doc },
+ { "get_progress", ZoneLoader_getProgress, METH_NOARGS,
+ ZoneLoader_getProgress_doc },
{ NULL, NULL, 0, NULL }
};
diff --git a/src/lib/python/isc/ddns/libddns_messages.mes b/src/lib/python/isc/ddns/libddns_messages.mes
index 7e34e70..406151c 100644
--- a/src/lib/python/isc/ddns/libddns_messages.mes
+++ b/src/lib/python/isc/ddns/libddns_messages.mes
@@ -122,7 +122,7 @@ as the class in the Zone Section, ANY, or NONE.
A FORMERR response is sent back to the client.
% LIBDDNS_UPDATE_DATASRC_ERROR error in datasource during DDNS update: %1
-An error occured while committing the DDNS update changes to the
+An error occurred while committing the DDNS update changes to the
datasource. The specific error is printed. A SERVFAIL response is sent
back to the client.
diff --git a/src/lib/python/isc/log/log.cc b/src/lib/python/isc/log/log.cc
index 7b95201..108efe3 100644
--- a/src/lib/python/isc/log/log.cc
+++ b/src/lib/python/isc/log/log.cc
@@ -40,7 +40,7 @@ using boost::bind;
// (tags/RELEASE_28 115909)) on OSX, where unwinding the stack
// segfaults the moment this exception was thrown and caught.
//
-// Placing it in a named namespace instead of the originalRecommend
+// Placing it in a named namespace instead of the original
// unnamed namespace appears to solve this, so as a temporary
// workaround, we create a local randomly named namespace here
// to solve this issue.
diff --git a/src/lib/python/isc/log_messages/Makefile.am b/src/lib/python/isc/log_messages/Makefile.am
index 2e86ba3..97ff6e6 100644
--- a/src/lib/python/isc/log_messages/Makefile.am
+++ b/src/lib/python/isc/log_messages/Makefile.am
@@ -17,6 +17,7 @@ EXTRA_DIST += libxfrin_messages.py
EXTRA_DIST += loadzone_messages.py
EXTRA_DIST += server_common_messages.py
EXTRA_DIST += dbutil_messages.py
+EXTRA_DIST += msgq_messages.py
CLEANFILES = __init__.pyc
CLEANFILES += bind10_messages.pyc
@@ -35,6 +36,7 @@ CLEANFILES += libxfrin_messages.pyc
CLEANFILES += loadzone_messages.pyc
CLEANFILES += server_common_messages.pyc
CLEANFILES += dbutil_messages.pyc
+CLEANFILES += msgq_messages.pyc
CLEANDIRS = __pycache__
diff --git a/src/lib/python/isc/log_messages/msgq_messages.py b/src/lib/python/isc/log_messages/msgq_messages.py
new file mode 100644
index 0000000..81efa41
--- /dev/null
+++ b/src/lib/python/isc/log_messages/msgq_messages.py
@@ -0,0 +1 @@
+from work.msgq_messages import *
diff --git a/src/lib/python/isc/statistics/Makefile.am b/src/lib/python/isc/statistics/Makefile.am
new file mode 100644
index 0000000..9be1312
--- /dev/null
+++ b/src/lib/python/isc/statistics/Makefile.am
@@ -0,0 +1,9 @@
+SUBDIRS = . tests
+
+python_PYTHON = __init__.py counters.py
+pythondir = $(pyexecdir)/isc/statistics
+
+CLEANDIRS = __pycache__
+
+clean-local:
+ rm -rf $(CLEANDIRS)
diff --git a/src/lib/python/isc/statistics/__init__.py b/src/lib/python/isc/statistics/__init__.py
new file mode 100644
index 0000000..9e77ed6
--- /dev/null
+++ b/src/lib/python/isc/statistics/__init__.py
@@ -0,0 +1 @@
+from isc.statistics.counters import *
diff --git a/src/lib/python/isc/statistics/counters.py b/src/lib/python/isc/statistics/counters.py
new file mode 100644
index 0000000..99b989d
--- /dev/null
+++ b/src/lib/python/isc/statistics/counters.py
@@ -0,0 +1,403 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""BIND 10 statistics counters module
+
+This module handles the statistics counters for BIND 10 modules. For
+using the module `counter.py`, first a counters object should be created
+in each module (like b10-xfrin or b10-xfrout) after importing this
+module. A spec file can be specified as an argument when creating the
+counters object:
+
+ from isc.statistics import Counters
+ self.counters = Counters("/path/to/foo.spec")
+
+The first argument of Counters() can be specified, which is the location
+of the specification file (like src/bin/xfrout/xfrout.spec). If Counters
+is constructed this way, statistics counters can be accessed from each
+module. For example, in case that the item `xfrreqdone` is defined in
+statistics_spec in xfrout.spec, the following methods are
+callable. Since these methods require the string of the zone name in the
+first argument, if we have the following code in b10-xfrout:
+
+ self.counters.inc('zones', zone_name, 'xfrreqdone')
+
+then the counter for xfrreqdone corresponding to zone_name is
+incremented. For getting the current number of this counter, we can use
+the following code:
+
+ number = self.counters.get('zones', zone_name, 'xfrreqdone')
+
+then the current count is obtained and set in the variable
+`number`. Such a getter method would be mainly used for unit-testing.
+As other example, for the item `axfr_running`, the decrementer method is
+also callable. This method is used for decrementing a counter. For the
+item `axfr_running`, an argument like zone name is not required:
+
+ self.counters.dec('axfr_running')
+
+These methods are effective in other modules. For example, in case that
+this module `counter.py` is once imported in a main module such as
+b10-xfrout, then for the item `notifyoutv4`, the `inc()` method can be
+invoked in another module such as notify_out.py, which is firstly
+imported in the main module.
+
+ self.counters.inc('zones', zone_name, 'notifyoutv4')
+
+In this example this is for incrementing the counter of the item
+`notifyoutv4`. Thus, such statement can be also written in another
+library like isc.notify.notify_out. If this module `counter.py` isn't
+imported in the main module but imported in such a library module as
+isc.notify.notify_out, in this example, empty methods would be invoked,
+which is directly defined in `counter.py`.
+"""
+
+import threading
+import isc.config
+from datetime import datetime
+
+# static internal functions
+def _add_counter(element, spec, identifier):
+ """Returns value of the identifier if the identifier is in the
+ element. Otherwise, sets a default value from the spec and
+ returns it. If the top-level type of the identifier is named_set
+ and the second-level type is map, it sets a set of default values
+ under the level and then returns the default value of the
+ identifier. This method raises DataNotFoundError if the element is
+ invalid for spec."""
+ try:
+ return isc.cc.data.find(element, identifier)
+ except isc.cc.data.DataNotFoundError:
+ pass
+ # check whether spec and identifier are correct
+ isc.config.find_spec_part(spec, identifier)
+ # examine spec of the top-level item first
+ spec_ = isc.config.find_spec_part(spec, identifier.split('/')[0])
+ if spec_['item_type'] == 'named_set' and \
+ spec_['named_set_item_spec']['item_type'] == 'map':
+ map_spec = spec_['named_set_item_spec']['map_item_spec']
+ for name in isc.config.spec_name_list(map_spec):
+ spec_ = isc.config.find_spec_part(map_spec, name)
+ id_str = '%s/%s/%s' % \
+ tuple(identifier.split('/')[0:2] + [name])
+ isc.cc.data.set(element, id_str, spec_['item_default'])
+ else:
+ spec_ = isc.config.find_spec_part(spec, identifier)
+ isc.cc.data.set(element, identifier, spec_['item_default'])
+ return isc.cc.data.find(element, identifier)
+
+def _set_counter(element, spec, identifier, value):
+ """Invokes _add_counter() for checking whether the identifier is
+ in the element. If not, it creates a new identifier in the element
+ and set the default value from the spec. After that, it sets the
+ value specified in the arguments."""
+ _add_counter(element, spec, identifier)
+ isc.cc.data.set(element, identifier, value)
+
+def _get_counter(element, identifier):
+ """Returns the value of the identifier in the element"""
+ return isc.cc.data.find(element, identifier)
+
+def _inc_counter(element, spec, identifier, step=1):
+ """Increments the value of the identifier in the element to the
+ step from the current value. If the identifier isn't in the
+ element, it creates a new identifier in the element."""
+ isc.cc.data.set(element, identifier,
+ _add_counter(element, spec, identifier) + step)
+
+def _start_timer():
+ """Returns the current datetime as a datetime object."""
+ return datetime.now()
+
+def _stop_timer(start_time, element, spec, identifier):
+ """Sets duration time in seconds as a value of the identifier in
+ the element, which is in seconds between start_time and the
+ current time and is float-type."""
+ delta = datetime.now() - start_time
+ # FIXME: The following statement can be replaced by:
+ # sec = delta.total_seconds()
+ # but total_seconds() is not available in Python 3.1. Please update
+ # this code when we depend on Python 3.2.
+ sec = round(delta.days * 86400 + delta.seconds + \
+ delta.microseconds * 1E-6, 6)
+ _set_counter(element, spec, identifier, sec)
+
+def _concat(*args, sep='/'):
+ """A helper function that is used to generate an identifier for
+ statistics item names. It concatenates words in args with a
+ separator('/')
+ """
+ return sep.join(args)
+
+class _Statistics():
+ """Statistics data set"""
+ # default statistics data
+ _data = {}
+ # default statistics spec used in case the specfile is omitted when
+ # constructing a Counters() object
+ _spec = [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": False,
+ "item_default": {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": False,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": False,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out"
+ }
+ ]
+ }
+ }
+ ]
+
+class Counters():
+ """A class for holding and manipulating all statistics counters
+ for a module. A Counters object may be created by specifying a spec
+ file of the module in argument. According to statistics
+ specification in the spec file, a counter value can be incremented,
+ decremented or obtained. Methods such as inc(), dec() and get() are
+ useful for this. Counters objects also have timer functionality.
+ The timer can be started and stopped, and the duration between
+ start and stop can be obtained. Methods such as start_timer(),
+ stop_timer() and get() are useful for this. Saved counters can be
+ cleared by the method clear_all(). Manipulating counters and
+ timers can be temporarily disabled. If disabled, counter values are
+ not changed even if methods to update them are invoked. Including
+ per-zone counters, a list of counters which can be handled in the
+ class are like the following:
+
+ zones/example.com./notifyoutv4
+ zones/example.com./notifyoutv6
+ zones/example.com./xfrrej
+ zones/example.com./xfrreqdone
+ zones/example.com./soaoutv4
+ zones/example.com./soaoutv6
+ zones/example.com./axfrreqv4
+ zones/example.com./axfrreqv6
+ zones/example.com./ixfrreqv4
+ zones/example.com./ixfrreqv6
+ zones/example.com./xfrsuccess
+ zones/example.com./xfrfail
+ zones/example.com./time_to_ixfr
+ zones/example.com./time_to_axfr
+ ixfr_running
+ axfr_running
+ socket/unixdomain/open
+ socket/unixdomain/openfail
+ socket/unixdomain/close
+ socket/unixdomain/bindfail
+ socket/unixdomain/acceptfail
+ socket/unixdomain/accept
+ socket/unixdomain/senderr
+ socket/unixdomain/recverr
+ socket/ipv4/tcp/open
+ socket/ipv4/tcp/openfail
+ socket/ipv4/tcp/close
+ socket/ipv4/tcp/connfail
+ socket/ipv4/tcp/conn
+ socket/ipv4/tcp/senderr
+ socket/ipv4/tcp/recverr
+ socket/ipv6/tcp/open
+ socket/ipv6/tcp/openfail
+ socket/ipv6/tcp/close
+ socket/ipv6/tcp/connfail
+ socket/ipv6/tcp/conn
+ socket/ipv6/tcp/senderr
+ socket/ipv6/tcp/recverr
+ """
+
+ # '_SERVER_' is a special zone name representing an entire
+ # count. It doesn't mean a specific zone, but it means an
+ # entire count in the server.
+ _entire_server = '_SERVER_'
+ # zone names are contained under this dirname in the spec file.
+ _perzone_prefix = 'zones'
+ # default statistics data set
+ _statistics = _Statistics()
+
+ def __init__(self, spec_file_name=None):
+ """A constructor for the Counters class. A path to the spec file
+ can be specified in spec_file_name. Statistics data based on
+ statistics spec can be accumulated if spec_file_name is
+ specified. If omitted, a default statistics spec is used. The
+ default statistics spec is defined in a hidden class named
+ _Statistics().
+ """
+ self._zones_item_list = []
+ self._start_time = {}
+ self._disabled = False
+ self._rlock = threading.RLock()
+ if not spec_file_name: return
+ # change the default statistics spec
+ self._statistics._spec = \
+ isc.config.module_spec_from_file(spec_file_name).\
+ get_statistics_spec()
+ if self._perzone_prefix in \
+ isc.config.spec_name_list(self._statistics._spec):
+ self._zones_item_list = isc.config.spec_name_list(
+ isc.config.find_spec_part(
+ self._statistics._spec, self._perzone_prefix)\
+ ['named_set_item_spec']['map_item_spec'])
+
+ def clear_all(self):
+ """clears all statistics data"""
+ with self._rlock:
+ self._statistics._data = {}
+
+ def disable(self):
+ """disables incrementing/decrementing counters"""
+ with self._rlock:
+ self._disabled = True
+
+ def enable(self):
+ """enables incrementing/decrementing counters"""
+ with self._rlock:
+ self._disabled = False
+
+ def _incdec(self, *args, step=1):
+ """A common helper function for incrementing or decrementing a
+ counter. It acquires a lock to support multi-threaded
+ use. isc.cc.data.DataNotFoundError is raised when incrementing
+ the counter of the item undefined in the spec file."""
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ _inc_counter(self._statistics._data,
+ self._statistics._spec,
+ identifier, step)
+
+ def inc(self, *args):
+ """An incrementer for a counter. It acquires a lock to support
+ multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+ incrementing the counter of the item undefined in the spec file."""
+ return self._incdec(*args)
+
+ def dec(self, *args):
+ """A decrementer for a counter. It acquires a lock to support
+ multi-threaded use. isc.cc.data.DataNotFoundError is raised when
+ decrementing the counter of the item undefined in the spec file."""
+ return self._incdec(*args, step=-1)
+
+ def get(self, *args):
+ """A getter method for counters. It returns the current number
+ of the specified counter. isc.cc.data.DataNotFoundError is
+ raised when the counter doesn't have a number yet."""
+ identifier = _concat(*args)
+ return _get_counter(self._statistics._data, identifier)
+
+ def start_timer(self, *args):
+ """Starts a timer which is identified by args and keeps it
+ running until stop_timer() is called. It acquires a lock to
+ support multi-threaded use."""
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ isc.cc.data.set(self._start_time, identifier, _start_timer())
+
+ def stop_timer(self, *args):
+ """Stops a timer which is identified by args. It acquires a lock
+ to support multi-threaded use. If the timer isn't started by
+ start_timer() yet, it raises no exception. However if args
+ aren't defined in the spec file, it raises DataNotFoundError.
+ """
+ identifier = _concat(*args)
+ with self._rlock:
+ if self._disabled: return
+ try:
+ start_time = isc.cc.data.find(self._start_time,
+ identifier)
+ except isc.cc.data.DataNotFoundError:
+ # do not set the end time if the timer isn't started
+ return
+ # set the end time
+ _stop_timer(
+ start_time,
+ self._statistics._data,
+ self._statistics._spec,
+ identifier)
+ # A datetime value of once used timer should be deleted
+ # for a future use.
+ # Here, names of branch and leaf are obtained from a
+ # string of identifier. The branch name is equivalent to
+ # the position of datetime to be deleted and the leaf name
+ # is equivalent to the value of datetime to be deleted.
+ (branch, leaf) = identifier.rsplit('/', 1)
+ # Then map of branch is obtained from self._start_time by
+ # using isc.cc.data.find().
+ branch_map = isc.cc.data.find(self._start_time, branch)
+ # Finally a value of the leaf name is deleted from the
+ # map.
+ del branch_map[leaf]
+
+ def get_statistics(self):
+ """Calculates an entire server's counts, and returns statistics
+ data in a format to send out to the stats module, including each
+ counter. If nothing is counted yet, then it returns an empty
+ dictionary."""
+ # entire copy
+ statistics_data = self._statistics._data.copy()
+ # If there is no 'zones' found in statistics_data,
+ # i.e. statistics_data contains no per-zone counter, it just
+ # returns statistics_data because calculating total counts
+ # across the zone names isn't necessary.
+ if self._perzone_prefix not in statistics_data:
+ return statistics_data
+ zones = statistics_data[self._perzone_prefix]
+ # Start calculation for '_SERVER_' counts
+ zones_spec = isc.config.find_spec_part(self._statistics._spec,
+ self._perzone_prefix)
+ zones_attrs = zones_spec['item_default'][self._entire_server]
+ zones_data = {}
+ for attr in zones_attrs:
+ id_str = '%s/%s' % (self._entire_server, attr)
+ sum_ = 0
+ for name in zones:
+ if attr in zones[name]:
+ sum_ += zones[name][attr]
+ if sum_ > 0:
+ _set_counter(zones_data, zones_spec,
+ id_str, sum_)
+ # insert entire-server counts
+ statistics_data[self._perzone_prefix] = dict(
+ statistics_data[self._perzone_prefix],
+ **zones_data)
+ return statistics_data
diff --git a/src/lib/python/isc/statistics/tests/Makefile.am b/src/lib/python/isc/statistics/tests/Makefile.am
new file mode 100644
index 0000000..8dcc296
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/Makefile.am
@@ -0,0 +1,33 @@
+PYCOVERAGE_RUN=@PYCOVERAGE_RUN@
+PYTESTS = counters_test.py
+EXTRA_DIST = $(PYTESTS)
+EXTRA_DIST += testdata/test_spec1.spec
+EXTRA_DIST += testdata/test_spec2.spec
+EXTRA_DIST += testdata/test_spec3.spec
+
+# If necessary (rare cases), explicitly specify paths to dynamic libraries
+# required by loadable python modules.
+LIBRARY_PATH_PLACEHOLDER =
+if SET_ENV_LIBRARY_PATH
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/cryptolink/.libs:$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/cc/.libs:$(abs_top_builddir)/src/lib/config/.libs:$(abs_top_builddir)/src/lib/log/.libs:$(abs_top_builddir)/src/lib/util/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/datasrc/.libs:$$$(ENV_LIBRARY_PATH)
+else
+# Some systems need the ds path even if not all paths are necessary
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/datasrc/.libs
+endif
+
+# test using command-line arguments, so use check-local target instead of TESTS
+check-local:
+if ENABLE_PYTHON_COVERAGE
+ touch $(abs_top_srcdir)/.coverage
+ rm -f .coverage
+ ${LN_S} $(abs_top_srcdir)/.coverage .coverage
+endif
+ for pytest in $(PYTESTS) ; do \
+ echo Running test: $$pytest ; \
+ PYTHONPATH=$(COMMON_PYTHON_PATH) \
+ $(LIBRARY_PATH_PLACEHOLDER) \
+ B10_FROM_BUILD=$(abs_top_builddir) \
+ B10_FROM_SOURCE=$(abs_top_srcdir) \
+ TESTDATASRCDIR=$(abs_top_srcdir)/src/lib/python/isc/statistics/tests/testdata \
+ $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ done
diff --git a/src/lib/python/isc/statistics/tests/counters_test.py b/src/lib/python/isc/statistics/tests/counters_test.py
new file mode 100644
index 0000000..ff15efc
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/counters_test.py
@@ -0,0 +1,416 @@
+# Copyright (C) 2012 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+'''Tests for isc.statistics.counter'''
+
+import unittest
+import threading
+from datetime import timedelta
+import os
+import imp
+import isc.config
+
+TEST_ZONE_NAME_STR = "example.com."
+TESTDATA_SRCDIR = os.getenv("TESTDATASRCDIR")
+
+from isc.statistics import counters
+
+def setup_functor(event, cycle, functor, *args):
+ """Waits until the event is started, and then invokes the functor
+ by times of the cycle with args."""
+ event.wait()
+ for i in range(cycle): functor(*args)
+
+def start_functor(concurrency, number, functor, *args):
+ """Creates the threads of the number and makes them start. Sets
+ the event and waits until these threads are finished."""
+ threads = []
+ event = threading.Event()
+ for i in range(concurrency):
+ threads.append(threading.Thread(\
+ target=setup_functor, \
+ args=(event, number, functor,) + args))
+ for th in threads: th.start()
+ event.set()
+ for th in threads: th.join()
+
+class TestBasicMethods(unittest.TestCase):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+
+ def setUp(self):
+ imp.reload(counters)
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+ def tearDown(self):
+ self.counters.clear_all()
+
+ def test_clear_counters(self):
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, 'counter')
+ self.counters.inc('counter')
+ self.assertEqual(self.counters.get('counter'), 1)
+ self.counters.clear_all()
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, 'counter')
+
+ def test_enablediable(self):
+ self.assertFalse(self.counters._disabled)
+ self.counters.disable()
+ self.assertTrue(self.counters._disabled)
+ self.counters.enable()
+ self.assertFalse(self.counters._disabled)
+
+ def test_add_counter_normal(self):
+ element = {'counter' : 1}
+ self.assertEqual(\
+ counters._add_counter(element, [], 'counter'), 1)
+
+ def test_add_counter_wrongspec(self):
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ counters._add_counter,
+ {}, [], 'counter')
+
+ def test_add_counter_empty(self):
+ self.assertEqual(\
+ counters._add_counter(
+ {},
+ [ { 'item_name' : 'counter',
+ 'item_type' : 'integer',
+ 'item_default' : 0 } ],
+ 'counter'), 0)
+
+ def test_add_counter_empty_namedset(self):
+ elem = {}
+ spec = [ { 'item_name': 'dirs',
+ 'item_type': 'named_set',
+ 'named_set_item_spec': {
+ 'item_name': 'dir',
+ 'item_type': 'map',
+ 'map_item_spec': [
+ { 'item_name': 'counter1',
+ 'item_type': 'integer',
+ 'item_default': 0 },
+ { 'item_name': 'counter2',
+ 'item_type': 'integer',
+ 'item_default': 0 } ]}
+ }]
+ self.assertEqual(\
+ counters._add_counter(elem, spec, 'dirs/foo/counter1'), 0)
+ self.assertEqual(\
+ counters._add_counter(elem, spec, 'dirs/bar/counter2'), 0)
+
+ def test_timer(self):
+ t1 = counters._start_timer()
+ t2 = t1 - timedelta(seconds=1)
+ self.assertEqual((t1 - t2).seconds, 1)
+ elem = {}
+ spec = [{ 'item_name': 'time',
+ 'item_type': 'real',
+ 'item_default': 0.0 }]
+ counters._stop_timer(t2, elem, spec, 'time')
+ self.assertGreater(counters._get_counter(elem,'time'), 1)
+
+ def test_rasing_incrementers(self):
+ """ use Thread"""
+ concurrency = 3 # number of the threads
+ number = 10000 # number of counting per thread
+ counter_name = "counter"
+ timer_name = "seconds"
+ start_time = counters._start_timer()
+ start_functor(concurrency, number, self.counters.inc,
+ counter_name)
+ counters._stop_timer(start_time,
+ self.counters._statistics._data,
+ self.counters._statistics._spec,
+ timer_name)
+ self.assertEqual(
+ counters._get_counter(self.counters._statistics._data,
+ counter_name),
+ concurrency * number)
+ self.assertGreater(
+ counters._get_counter(self.counters._statistics._data,
+ timer_name), 0)
+
+ def test_concat(self):
+ # only strings
+ a = ( 'a','b','c','d' )
+ self.assertEqual('a/b/c/d', counters._concat(*a))
+ self.assertEqual('aTbTcTd', counters._concat(*a, sep='T'))
+ self.assertEqual('a\\b\\c\\d', counters._concat(*a, sep='\\'))
+ # mixed with other types
+ b = a + (1,)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + (1.1,)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + ([],)
+ self.assertRaises(TypeError, counters._concat, *b)
+ b = a + ({},)
+ self.assertRaises(TypeError, counters._concat, *b)
+
+class BaseTestCounters():
+
+ def setUp(self):
+ imp.reload(counters)
+ self._statistics_data = {}
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+ self._entire_server = self.counters._entire_server
+ self._perzone_prefix = self.counters._perzone_prefix
+
+ def tearDown(self):
+ self.counters.clear_all()
+
+ def check_get_statistics(self):
+ """Checks no differences between the value returned from
+ get_statistics() and locally collected statistics data. Also
+ checks the result isn't changed even after the method is
+ invoked twice. Finally checks it is valid for the the
+ statistics spec."""
+ self.assertEqual(self.counters.get_statistics(),
+ self._statistics_data)
+ # Idempotency check
+ self.assertEqual(self.counters.get_statistics(),
+ self._statistics_data)
+ if self.TEST_SPECFILE_LOCATION:
+ self.assertTrue(isc.config.module_spec_from_file(
+ self.TEST_SPECFILE_LOCATION).validate_statistics(
+ False, self._statistics_data))
+ else:
+ self.assertTrue(isc.config.ModuleSpec(
+ {'module_name': 'Foo',
+ 'statistics': self.counters._statistics._spec}
+ ).validate_statistics(
+ False, self._statistics_data))
+
+ def test_perzone_counters(self):
+ # for per-zone counters
+ for name in self.counters._zones_item_list:
+ args = (self._perzone_prefix, TEST_ZONE_NAME_STR, name)
+ if name.find('time_to_') == 0:
+ self.counters.start_timer(*args)
+ self.counters.stop_timer(*args)
+ self.assertGreater(self.counters.get(*args), 0)
+ sec = self.counters.get(*args)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s' % (args[0], zone_str, name), sec)
+ # twice exec stopper, then second is not changed
+ self.counters.stop_timer(*args)
+ self.assertEqual(self.counters.get(*args), sec)
+ else:
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ for zone_str in (self._entire_server, TEST_ZONE_NAME_STR):
+ isc.cc.data.set(self._statistics_data,
+ '%s/%s/%s' % (args[0], zone_str, name), 2)
+ self.check_get_statistics()
+
+ def test_xfrrunning_counters(self):
+ # for counters of xfer running
+ _suffix = 'xfr_running'
+ _xfrrunning_names = \
+ isc.config.spec_name_list(self.counters._statistics._spec,
+ "", True)
+ for name in _xfrrunning_names:
+ if name.find(_suffix) != 1: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.disable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.dec(*args)
+ self.assertEqual(self.counters.get(*args), 0)
+ self._statistics_data[name] = 0
+ self.check_get_statistics()
+
+ def test_socket_counters(self):
+ # for ipsocket/unixsocket counters
+ _prefix = 'socket/'
+ _socket_names = \
+ isc.config.spec_name_list(self.counters._statistics._spec,
+ "", True)
+ for name in _socket_names:
+ if name.find(_prefix) != 0: continue
+ args = name.split('/')
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ # checks disable/enable
+ self.counters.disable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 1)
+ self.counters.enable()
+ self.counters.inc(*args)
+ self.assertEqual(self.counters.get(*args), 2)
+ isc.cc.data.set(
+ self._statistics_data, '/'.join(args), 2)
+ self.check_get_statistics()
+
+ def test_undefined_item(self):
+ # test DataNotFoundError raising when specifying item defined
+ # in the specfile
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.inc, '__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.dec, '__undefined__')
+ self.counters.start_timer('__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.stop_timer, '__undefined__')
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.counters.get, '__undefined__')
+
+class TestCounters0(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = None
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters1(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec1.spec'
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters2(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class TestCounters3(unittest.TestCase, BaseTestCounters):
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec3.spec'
+ @classmethod
+ def setUpClass(cls):
+ imp.reload(counters)
+ def setUp(self):
+ BaseTestCounters.setUp(self)
+ def tearDown(self):
+ BaseTestCounters.tearDown(self)
+
+class BaseDummyModule():
+ """A base dummy class"""
+ TEST_SPECFILE_LOCATION = TESTDATA_SRCDIR + os.sep + 'test_spec2.spec'
+ def __init__(self):
+ self.counters = counters.Counters(self.TEST_SPECFILE_LOCATION)
+
+ def get_counters(self):
+ return self.counters.get_statistics()
+
+ def clear_counters(self):
+ self.counters.clear_all()
+
+class DummyNotifyOut(BaseDummyModule):
+ """A dummy class equivalent to notify.notify_out.NotifyOut"""
+ def __init__(self):
+ self.counters = counters.Counters()
+
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv4')
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'notifyoutv6')
+
+class DummyXfroutSession(BaseDummyModule):
+ """A dummy class equivalent to XfroutSession in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrreqdone')
+ self.counters.inc('zones', TEST_ZONE_NAME_STR, 'xfrrej')
+ self.counters.inc('axfr_running')
+ self.counters.inc('ixfr_running')
+ self.counters.dec('axfr_running')
+ self.counters.dec('ixfr_running')
+
+class DummyUnixSockServer(BaseDummyModule):
+ """A dummy class equivalent to UnixSockServer in b10-xfrout"""
+ def inc_counters(self):
+ """increments counters"""
+ self.counters.inc('socket', 'unixdomain', 'open')
+ self.counters.inc('socket', 'unixdomain', 'close')
+
+class DummyXfroutServer(BaseDummyModule):
+ """A dummy class equivalent to XfroutServer in b10-xfrout"""
+ def __init__(self):
+ super().__init__()
+ self.xfrout_sess = DummyXfroutSession()
+ self.unix_socket_server = DummyUnixSockServer()
+ self.notifier = DummyNotifyOut()
+
+ def inc_counters(self):
+ self.xfrout_sess.inc_counters()
+ self.unix_socket_server.inc_counters()
+ self.notifier.inc_counters()
+
+class TestDummyNotifyOut(unittest.TestCase):
+ """Tests counters are incremented in which the spec file is not
+ loaded"""
+ def setUp(self):
+ imp.reload(counters)
+ self.notifier = DummyNotifyOut()
+ self.notifier.inc_counters()
+
+ def tearDown(self):
+ self.notifier.clear_counters()
+
+ def test_counters(self):
+ self.assertEqual(
+ {'zones': {'_SERVER_': {'notifyoutv4': 1, 'notifyoutv6': 1},
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1, 'notifyoutv6': 1}}},
+ self.notifier.get_counters())
+
+class TestDummyXfroutServer(unittest.TestCase):
+ """Tests counters are incremented or decremented in which the same
+ spec file is multiply loaded in each child class"""
+ def setUp(self):
+ imp.reload(counters)
+ self.xfrout_server = DummyXfroutServer()
+ self.xfrout_server.inc_counters()
+
+ def tearDown(self):
+ self.xfrout_server.clear_counters()
+
+ def test_counters(self):
+ self.assertEqual(
+ {'axfr_running': 0, 'ixfr_running': 0,
+ 'socket': {'unixdomain': {'open': 1, 'close': 1}},
+ 'zones': {'_SERVER_': {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1, 'xfrreqdone': 1},
+ TEST_ZONE_NAME_STR: {'notifyoutv4': 1,
+ 'notifyoutv6': 1,
+ 'xfrrej': 1,
+ 'xfrreqdone': 1}}},
+ self.xfrout_server.get_counters())
+
+if __name__== "__main__":
+ unittest.main()
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
new file mode 100644
index 0000000..dbe7c6c
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec1.spec
@@ -0,0 +1,26 @@
+{
+ "module_spec": {
+ "module_name": "Simple",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "counter",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_title": "Counter",
+ "item_description": "Counter",
+ "item_default": 0
+ },
+ {
+ "item_name": "seconds",
+ "item_type": "real",
+ "item_optional": false,
+ "item_title": "Seconds",
+ "item_description": "Seconds",
+ "item_default": 0.0
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
new file mode 100644
index 0000000..11b8706
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec2.spec
@@ -0,0 +1,187 @@
+{
+ "module_spec": {
+ "module_name": "XfroutLike",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {
+ "_SERVER_" : {
+ "notifyoutv4" : 0,
+ "notifyoutv6" : 0,
+ "xfrrej" : 0,
+ "xfrreqdone" : 0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "notifyoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv4 notifies",
+ "item_description": "Number of IPv4 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "notifyoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IPv6 notifies",
+ "item_description": "Number of IPv6 notifies per zone name sent out from Xfrout"
+ },
+ {
+ "item_name": "xfrrej",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XFR rejected requests",
+ "item_description": "Number of XFR requests per zone name rejected by Xfrout"
+ },
+ {
+ "item_name": "xfrreqdone",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Requested zone transfers",
+ "item_description": "Number of requested zone transfers completed per zone name"
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "ixfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFR running",
+ "item_description": "Number of IXFRs in progress"
+ },
+ {
+ "item_name": "axfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFR running",
+ "item_description": "Number of AXFRs in progress"
+ },
+ {
+ "item_name": "socket",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "unixdomain": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "bindfail": 0,
+ "acceptfail": 0,
+ "accept": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "Socket",
+ "item_description": "Socket",
+ "map_item_spec": [
+ {
+ "item_name": "unixdomain",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "bindfail": 0,
+ "acceptfail": 0,
+ "accept": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "Unix Domain",
+ "item_description": "Unix Domain",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "Unix Domain sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "Unix Domain sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "Unix Domain sockets closed"
+ },
+ {
+ "item_name": "bindfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Bind failures",
+ "item_description": "Unix Domain sockets bind failures"
+ },
+ {
+ "item_name": "acceptfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Accept failures",
+ "item_description": "Unix Domain sockets incoming accept failures"
+ },
+ {
+ "item_name": "accept",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Accept",
+ "item_description": "Unix Domain sockets incoming connections successfully accepted"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "Unix Domain sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "Unix Domain sockets receive errors"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
+
diff --git a/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
new file mode 100644
index 0000000..c97a09a
--- /dev/null
+++ b/src/lib/python/isc/statistics/tests/testdata/test_spec3.spec
@@ -0,0 +1,382 @@
+{
+ "module_spec": {
+ "module_name": "XfrinLike",
+ "module_description": "XFR in daemon",
+ "config_data": [],
+ "commands": [],
+ "statistics": [
+ {
+ "item_name": "zones",
+ "item_type": "named_set",
+ "item_optional": false,
+ "item_default": {
+ "_SERVER_" : {
+ "soaoutv4": 0,
+ "soaoutv6": 0,
+ "axfrreqv4": 0,
+ "axfrreqv6": 0,
+ "ixfrreqv4": 0,
+ "ixfrreqv6": 0,
+ "xfrsuccess": 0,
+ "xfrfail": 0,
+ "time_to_ixfr": 0.0,
+ "time_to_axfr": 0.0
+ }
+ },
+ "item_title": "Zone names",
+ "item_description": "Zone names for Xfrout statistics",
+ "named_set_item_spec": {
+ "item_name": "zonename",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "item_title": "Zone name",
+ "item_description": "Zone name for Xfrout statistics",
+ "map_item_spec": [
+ {
+ "item_name": "soaoutv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv4",
+ "item_description": "Number of IPv4 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "soaoutv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOAOutv6",
+ "item_description": "Number of IPv6 SOA queries sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv4",
+ "item_description": "Number of IPv4 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "axfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRReqv6",
+ "item_description": "Number of IPv6 AXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv4",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv4",
+ "item_description": "Number of IPv4 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "ixfrreqv6",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRReqv6",
+ "item_description": "Number of IPv6 IXFR requests sent from Xfrin"
+ },
+ {
+ "item_name": "xfrsuccess",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrSuccess",
+ "item_description": "Number of zone transfer requests succeeded"
+ },
+ {
+ "item_name": "xfrfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "XfrFail",
+ "item_description": "Number of zone transfer requests failed"
+ },
+ {
+ "item_name": "time_to_ixfr",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Time to IXFR",
+ "item_description": "Elapsed time in seconds to do the last IXFR"
+ },
+ {
+ "item_name": "time_to_axfr",
+ "item_type": "real",
+ "item_optional": false,
+ "item_default": 0.0,
+ "item_title": "Time to AXFR",
+ "item_description": "Elapsed time in seconds to do the last AXFR"
+ }
+ ]
+ }
+ },
+ {
+ "item_name": "ixfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRs running",
+ "item_description": "Number of IXFRs in progress"
+ },
+ {
+ "item_name": "axfr_running",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRs running",
+ "item_description": "Number of AXFRs in progress"
+ },
+ {
+ "item_name": "ixfr_deferred",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "IXFRs deferred",
+ "item_description": "Number of deferred IXFRs"
+ },
+ {
+ "item_name": "axfr_deferred",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "AXFRs deferred",
+ "item_description": "Number of deferred AXFRs"
+ },
+ {
+ "item_name": "soa_in_progress",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "SOA queries",
+ "item_description": "Number of SOA queries in progress"
+ },
+ {
+ "item_name": "socket",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "ipv4": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "ipv6": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ }
+ },
+ "item_title": "Socket",
+ "item_description": "Socket",
+ "map_item_spec": [
+ {
+ "item_name": "ipv4",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "IPv4",
+ "item_description": "IPv4",
+ "map_item_spec": [
+ {
+ "item_name": "tcp",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "TCP",
+ "item_description": "TCP/IP",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "IPv4 TCP sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "IPv4 TCP sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "IPv4 TCP sockets closed"
+ },
+ {
+ "item_name": "connfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect failures",
+ "item_description": "IPv4 TCP sockets connection failures"
+ },
+ {
+ "item_name": "conn",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect",
+ "item_description": "IPv4 TCP connections established successfully"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "IPv4 TCP sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "IPv4 TCP sockets receive errors"
+ }
+ ]
+ }
+ ]
+ },
+ {
+ "item_name": "ipv6",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "tcp": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ }
+ },
+ "item_title": "IPv6",
+ "item_description": "IPv6",
+ "map_item_spec": [
+ {
+ "item_name": "tcp",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {
+ "open": 0,
+ "openfail": 0,
+ "close": 0,
+ "connfail": 0,
+ "conn": 0,
+ "senderr": 0,
+ "recverr": 0
+ },
+ "item_title": "TCP",
+ "item_description": "TCP/IP",
+ "map_item_spec": [
+ {
+ "item_name": "open",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open",
+ "item_description": "IPv6 TCP sockets opened successfully"
+ },
+ {
+ "item_name": "openfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Open failures",
+ "item_description": "IPv6 TCP sockets open failures"
+ },
+ {
+ "item_name": "close",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Close",
+ "item_description": "IPv6 TCP sockets closed"
+ },
+ {
+ "item_name": "connfail",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect failures",
+ "item_description": "IPv6 TCP sockets connection failures"
+ },
+ {
+ "item_name": "conn",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Connect",
+ "item_description": "IPv6 TCP connections established successfully"
+ },
+ {
+ "item_name": "senderr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Send errors",
+ "item_description": "IPv6 TCP sockets send errors"
+ },
+ {
+ "item_name": "recverr",
+ "item_type": "integer",
+ "item_optional": false,
+ "item_default": 0,
+ "item_title": "Receive errors",
+ "item_description": "IPv6 TCP sockets receive errors"
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/src/lib/resolve/resolve_messages.mes b/src/lib/resolve/resolve_messages.mes
index 1df544a..6447082 100644
--- a/src/lib/resolve/resolve_messages.mes
+++ b/src/lib/resolve/resolve_messages.mes
@@ -86,7 +86,7 @@ SERVFAIL will be returned.
% RESLIB_NOTSINGLE_RESPONSE response to query for <%1> was not a response
A debug message, the response to the specified query from an upstream
-nameserver was a CNAME that had mutiple RRs in the RRset. This is
+nameserver was a CNAME that had multiple RRs in the RRset. This is
an invalid response according to the standards so a SERVFAIL will be
returned to the system making the original query.
@@ -138,7 +138,7 @@ A debug message, the response to the specified query indicated an error
that is not covered by a specific code path. A SERVFAIL will be returned.
% RESLIB_RECQ_CACHE_FIND found <%1> in the cache (resolve() instance %2)
-This is a debug message and indicates that a RecursiveQuery object found the
+This is a debug message and indicates that a RecursiveQuery object found
the specified <name, class, type> tuple in the cache. The instance number
at the end of the message indicates which of the two resolve() methods has
been called.
@@ -178,7 +178,7 @@ A debug message giving the round-trip time of the last query and response.
This is a debug message and indicates that a RunningQuery object found
the specified <name, class, type> tuple in the cache.
-% RESLIB_RUNQ_CACHE_LOOKUP looking up up <%1> in the cache
+% RESLIB_RUNQ_CACHE_LOOKUP looking up <%1> in the cache
This is a debug message and indicates that a RunningQuery object has made
a call to its doLookup() method to look up the specified <name, class, type>
tuple, the first action of which will be to examine the cache.
diff --git a/src/lib/server_common/server_common_messages.mes b/src/lib/server_common/server_common_messages.mes
index 0e4efa5..22ce0f3 100644
--- a/src/lib/server_common/server_common_messages.mes
+++ b/src/lib/server_common/server_common_messages.mes
@@ -17,11 +17,11 @@ $NAMESPACE isc::server_common
# \brief Messages for the server_common library
% SOCKETREQUESTOR_CREATED Socket requestor created for application %1
-Debug message. A socket requesor (client of the socket creator) is created
+Debug message. A socket requestor (client of the socket creator) is created
for the corresponding application. Normally this should happen at most
one time throughout the lifetime of the application.
-% SOCKETREQUESTOR_DESTROYED Socket requestor destoryed
+% SOCKETREQUESTOR_DESTROYED Socket requestor destroyed
Debug message. The socket requestor created at SOCKETREQUESTOR_CREATED
has been destroyed. This event is generally unexpected other than in
test cases.
diff --git a/src/lib/testutils/dnsmessage_test.h b/src/lib/testutils/dnsmessage_test.h
index 262d177..6de01ec 100644
--- a/src/lib/testutils/dnsmessage_test.h
+++ b/src/lib/testutils/dnsmessage_test.h
@@ -208,10 +208,9 @@ pullSigs(std::vector<isc::dns::ConstRRsetPtr>& rrsets,
{
for (ITERATOR it = begin; it != end; ++it) {
rrsets.push_back(*it);
- text += (*it)->toText();
+ text += (*it)->toText(); // this will include RRSIG, if attached.
if ((*it)->getRRsig()) {
rrsets.push_back((*it)->getRRsig());
- text += (*it)->getRRsig()->toText();
}
}
}
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index d686da6..7b0b1bb 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.cc
@@ -27,6 +27,8 @@
#include <testutils/dnsmessage_test.h>
#include <testutils/srv_test.h>
+#include <boost/scoped_ptr.hpp>
+
using namespace isc::dns;
using namespace isc::util;
using namespace isc::asiolink;
@@ -48,27 +50,20 @@ SrvTestBase::SrvTestBase() : request_message(Message::RENDER),
response_obuffer(new OutputBuffer(0))
{}
-SrvTestBase::~SrvTestBase() {
- delete io_message;
- delete endpoint;
-}
-
void
SrvTestBase::createDataFromFile(const char* const datafile,
const int protocol)
{
- delete io_message;
data.clear();
- delete endpoint;
-
- endpoint = IOEndpoint::create(protocol,
- IOAddress(DEFAULT_REMOTE_ADDRESS),
- DEFAULT_REMOTE_PORT);
+ endpoint.reset(IOEndpoint::create(protocol,
+ IOAddress(DEFAULT_REMOTE_ADDRESS),
+ DEFAULT_REMOTE_PORT));
UnitTestUtil::readWireData(datafile, data);
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
- io_message = new IOMessage(&data[0], data.size(), *io_sock, *endpoint);
+ io_message.reset(new IOMessage(&data[0], data.size(), *io_sock,
+ *endpoint));
}
void
@@ -83,16 +78,14 @@ SrvTestBase::createRequestPacket(Message& message,
message.toWire(request_renderer, *context);
}
- delete io_message;
-
- endpoint = IOEndpoint::create(protocol, IOAddress(remote_address),
- remote_port);
+ endpoint.reset(IOEndpoint::create(protocol, IOAddress(remote_address),
+ remote_port));
io_sock = (protocol == IPPROTO_UDP) ? &IOSocket::getDummyUDPSocket() :
&IOSocket::getDummyTCPSocket();
- io_message = new IOMessage(request_renderer.getData(),
- request_renderer.getLength(),
- *io_sock, *endpoint);
+ io_message.reset(new IOMessage(request_renderer.getData(),
+ request_renderer.getLength(),
+ *io_sock, *endpoint));
}
// Unsupported requests. Should result in NOTIMP.
@@ -151,7 +144,7 @@ SrvTestBase::shortMessage() {
// or malformed or could otherwise cause a protocol error.
void
SrvTestBase::response() {
- // A valid (although unusual) response
+ // A valid (although unusual) response
createDataFromFile("simpleresponse_fromWire.wire");
processMessage();
EXPECT_FALSE(dnsserv.hasAnswer());
@@ -242,6 +235,6 @@ SrvTestBase::axfrOverUDP() {
} // end of namespace isc
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/src/lib/testutils/srv_test.h b/src/lib/testutils/srv_test.h
index e867595..a37d79b 100644
--- a/src/lib/testutils/srv_test.h
+++ b/src/lib/testutils/srv_test.h
@@ -25,6 +25,7 @@
#include <dns/rrtype.h>
#include "mockups.h"
+#include <boost/scoped_ptr.hpp>
namespace asiolink {
class IOSocket;
@@ -52,7 +53,6 @@ extern const unsigned int CD_FLAG;
class SrvTestBase : public ::testing::Test {
protected:
SrvTestBase();
- virtual ~SrvTestBase();
/// Let the server process a DNS message.
///
@@ -104,8 +104,8 @@ protected:
const isc::dns::RRClass qclass;
const isc::dns::RRType qtype;
asiolink::IOSocket* io_sock;
- asiolink::IOMessage* io_message;
- const asiolink::IOEndpoint* endpoint;
+ boost::scoped_ptr<asiolink::IOMessage> io_message;
+ boost::scoped_ptr<const asiolink::IOEndpoint> endpoint;
isc::dns::MessageRenderer request_renderer;
isc::util::OutputBufferPtr response_obuffer;
std::vector<uint8_t> data;
@@ -114,6 +114,6 @@ protected:
} // end of namespace isc
#endif // ISC_TESTUTILS_SRVTEST_H
-// Local Variables:
+// Local Variables:
// mode: c++
-// End:
+// End:
diff --git a/tests/lettuce/features/msgq.feature b/tests/lettuce/features/msgq.feature
new file mode 100644
index 0000000..19973f4
--- /dev/null
+++ b/tests/lettuce/features/msgq.feature
@@ -0,0 +1,18 @@
+Feature: Message queue tests
+ Tests for the message queue daemon.
+
+ Scenario: logging
+ # We check the message queue logs.
+ Given I have bind10 running with configuration default.config
+ And wait for bind10 stderr message BIND10_STARTED_CC
+ And wait for bind10 stderr message MSGQ_START
+ And wait for bind10 stderr message MSGQ_LISTENER_STARTED
+ And wait for bind10 stderr message MSGQ_CFGMGR_SUBSCRIBED
+ And wait for bind10 stderr message CMDCTL_STARTED
+
+ # Check it handles configuration. The configuration is invalid,
+ # but it should get there anyway and we abuse it.
+ # TODO: Once it has any kind of real command or configuration
+ # value, use that instead.
+ Then set bind10 configuration Msgq to {"nonsense": 1}
+ And wait for bind10 stderr message MSGQ_CONFIG_DATA
diff --git a/tests/system/ifconfig.sh b/tests/system/ifconfig.sh
index c0c365a..7c695e2 100755
--- a/tests/system/ifconfig.sh
+++ b/tests/system/ifconfig.sh
@@ -63,7 +63,7 @@ esac
case "$1" in
start|up)
- for ns in 1 2 3 4 5 6 7
+ for ns in 1 2 3 4 5 6 7 8
do
if test -n "$base"
then
@@ -145,7 +145,7 @@ case "$1" in
;;
stop|down)
- for ns in 7 6 5 4 3 2 1
+ for ns in 8 7 6 5 4 3 2 1
do
if test -n "$base"
then
diff --git a/tests/tools/perfdhcp/perf_pkt4.cc b/tests/tools/perfdhcp/perf_pkt4.cc
index 3ccef94..8b7e974 100644
--- a/tests/tools/perfdhcp/perf_pkt4.cc
+++ b/tests/tools/perfdhcp/perf_pkt4.cc
@@ -13,7 +13,7 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <dhcp/libdhcp++.h>
-#include <dhcp/dhcp6.h>
+#include <dhcp/dhcp4.h>
#include "perf_pkt4.h"
diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc
index 9376972..f7f4978 100644
--- a/tests/tools/perfdhcp/test_control.cc
+++ b/tests/tools/perfdhcp/test_control.cc
@@ -1284,6 +1284,10 @@ TestControl::sendDiscover4(const TestControlSocket& socket,
if (!pkt4) {
isc_throw(Unexpected, "failed to create DISCOVER packet");
}
+
+ // Delete the default Message Type option set by Pkt4
+ pkt4->delOption(DHO_DHCP_MESSAGE_TYPE);
+
// Set options: DHCP_MESSAGE_TYPE and DHCP_PARAMETER_REQUEST_LIST
OptionBuffer buf_msg_type;
buf_msg_type.push_back(DHCPDISCOVER);
@@ -1371,11 +1375,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
const dhcp::Pkt4Ptr& offer_pkt4) {
const uint32_t transid = generateTransid();
Pkt4Ptr pkt4(new Pkt4(DHCPREQUEST, transid));
- OptionBuffer buf_msg_type;
- buf_msg_type.push_back(DHCPREQUEST);
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- buf_msg_type);
- pkt4->addOption(opt_msg_type);
+
// Use first flags indicates that we want to use the server
// id captured in first packet.
if (CommandOptions::instance().isUseFirst() &&
@@ -1414,9 +1414,7 @@ TestControl::sendRequest4(const TestControlSocket& socket,
setDefaults4(socket, pkt4);
// Set hardware address
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
- pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address);
+ pkt4->setHWAddr(offer_pkt4->getHWAddr());
// Set elapsed time.
uint32_t elapsed_time = getElapsedTime<Pkt4Ptr>(discover_pkt4, offer_pkt4);
pkt4->setSecs(static_cast<uint16_t>(elapsed_time / 1000));
@@ -1461,8 +1459,10 @@ TestControl::sendRequest4(const TestControlSocket& socket,
transid));
// Set hardware address from OFFER packet received.
- const uint8_t* chaddr = offer_pkt4->getChaddr();
- std::vector<uint8_t> mac_address(chaddr, chaddr + HW_ETHER_LEN);
+ HWAddrPtr hwaddr = offer_pkt4->getHWAddr();
+ std::vector<uint8_t> mac_address(HW_ETHER_LEN, 0);
+ uint8_t hw_len = hwaddr->hwaddr_.size();
+ memcpy(&mac_address[0], &hwaddr->hwaddr_[0], hw_len > 16 ? 16 : hw_len);
pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end());
// Set elapsed time.
diff --git a/tests/tools/perfdhcp/tests/.gitignore b/tests/tools/perfdhcp/tests/.gitignore
index d6d1ec8..5697f95 100644
--- a/tests/tools/perfdhcp/tests/.gitignore
+++ b/tests/tools/perfdhcp/tests/.gitignore
@@ -1 +1,6 @@
/run_unittests
+/test1.hex
+/test2.hex
+/test3.hex
+/test4.hex
+/test5.hex
diff --git a/tests/tools/perfdhcp/tests/test_control_unittest.cc b/tests/tools/perfdhcp/tests/test_control_unittest.cc
index 6c27731..ae67e6e 100644
--- a/tests/tools/perfdhcp/tests/test_control_unittest.cc
+++ b/tests/tools/perfdhcp/tests/test_control_unittest.cc
@@ -630,13 +630,10 @@ private:
boost::shared_ptr<Pkt4>
createOfferPkt4(uint32_t transid) const {
boost::shared_ptr<Pkt4> offer(new Pkt4(DHCPOFFER, transid));
- OptionPtr opt_msg_type = Option::factory(Option::V4, DHO_DHCP_MESSAGE_TYPE,
- OptionBuffer(DHCPOFFER));
OptionPtr opt_serverid = Option::factory(Option::V4,
DHO_DHCP_SERVER_IDENTIFIER,
OptionBuffer(4, 1));
offer->setYiaddr(asiolink::IOAddress("127.0.0.1"));
- offer->addOption(opt_msg_type);
offer->addOption(opt_serverid);
offer->updateTimestamp();
return (offer);
More information about the bind10-changes
mailing list