BIND 10 trac598, updated. 91bf551afc20173718fdbbd49883b07ab1bc0e04 [trac598] Merge master to trac598
BIND 10 source code commits
bind10-changes at lists.isc.org
Fri Apr 22 06:02:45 UTC 2011
The branch, trac598 has been updated
via 91bf551afc20173718fdbbd49883b07ab1bc0e04 (commit)
via 047cdc39aa335b778d4ba9ecdacbd859d612807c (commit)
via e23c4b1e90ba1127a5b7eaef4add6f3b68fbc6fa (commit)
via a83e29a881af14dc61b366024e25f5ca68244220 (commit)
via bf0a9b3576da038ee53bd49360fe3bd8b01189fd (commit)
via 4e47d5f6b692c63c907af6681a75024450884a88 (commit)
via ad04d070072126a8f221f67244e9a54e68b53955 (commit)
via fb99e325e667a39ef88b6c8fab128a8df862a786 (commit)
via 31b96198427b2c140556afcd61ff3809c43266f6 (commit)
via 0a2a239a12af8c0ee54d62cb34d170c6cfdd33f9 (commit)
via 0e519f4da5ef99475c3bf5941ed1ea553a7c9a6e (commit)
via 429ade576746b5c5e1d9113ae57ee863276809b1 (commit)
via 25fe349f4fb12f444c06fdc2117a4f62f1dda2f7 (commit)
via fcc5a1f450a3a0125e999a8bc9e1d26f207a8153 (commit)
via 71d23aa4207048dc78531ce48e35e6a83a6b62ab (commit)
via bcc0502a221ec06331c2825c5e27874626b32cf1 (commit)
via 6555863f88c4e9865baebe0b55db1f7b00b6214c (commit)
via d3dbda9445dcb699e48457e89b90c3be933ec2b9 (commit)
via ca4c3ad8a436b2c5660959d266c82b692271c158 (commit)
via a2d07257bc12397054a57d4190cc61419e572278 (commit)
via 2e92b2f8a977a376fa38e3a51c9646ea9e83c4b8 (commit)
via ce281e646be9f0f273229d94ccd75bf7e08d17cf (commit)
via ca268cc4ca8f7505b996fa3076959b5ef3934658 (commit)
via c4d150ae4528f15fe8f05dbc85a1025abc92e78a (commit)
via ad3dfb04a335ed4c65722063bd351be51dae33b4 (commit)
via 5861ae7cb08e698c7a6c6d053b644f04106ed7a3 (commit)
via 778e7b5aa3c55ffc7947454459fd02263725303d (commit)
via bef9fc00a6c4e6c45922848d7f220ed262e83b1f (commit)
via d622e35a3d7b378f79dc4d587156cbfec3befed9 (commit)
via 433ef14c7c54f255b302ff74213a24bdc298f2a0 (commit)
via 5eeba2034481f043369f9a7f3f86ce606653387e (commit)
via 86ab4d3fabd2e551f90603d81ff2bce2368ea7af (commit)
via 865d62e9e58a3acf270f163a75ba713c134d82d2 (commit)
via aac391dda57b88507b8b10ac4bd1b068d3701b2d (commit)
via 3b6ddc0b213a61abf826d87351d97de9145a3fc4 (commit)
via 28b01ad5bf72472c824a7b8fc4a8dc394e22e462 (commit)
via 745b08afcdce71ca9b50b2d5a7ffe5d4ffa66091 (commit)
via b509cbb77d31e388df68dfe52709d6edef93df3f (commit)
via d12560d1714fb3a07e56b6903141d82d6081148d (commit)
via ba6927dc78e991f8a5c608beee4c9807895914cd (commit)
via 21718d6ffdc3f782ffa1b5960088786da9b77e4e (commit)
via 20f12e8672c45540cd72bb5df44cf21ff3b6732f (commit)
via 583d7973d8f49c061dee13a14c89cf0846f84721 (commit)
via 7b2536d7a654ecb24d98c3c0814c7384230e81df (commit)
via a3f3377467d511fb0e789957a4b7a9105e4fe0d2 (commit)
via e7d0dbd6ba745bab334aef6e747056c33f5edaa8 (commit)
via 7c1e4d5e1e28e556b1d10a8df8d9486971a3f052 (commit)
via 4929dc565823e49c94b1efea1543ba23d9745c9f (commit)
via c7bb38c99c9afa049c7336ea3f827aa41a1b2ea0 (commit)
via 1c269cbdc76f5dc2baeb43387c4d7ccc6dc863d2 (commit)
via 98e93921ae905aefef6646fc8839e3f4a30a37e1 (commit)
via 1ec7e1ccd24b25501902e0fc29fddf400f2ebdb1 (commit)
via 47b51ddc032a458b42454c3ed774bf26ef4254c1 (commit)
via c139d4bf6b30d7ad9325c7a621911c9455826f6b (commit)
via d8c55247d888cd9f5e2e474e49f8bd97b96623cf (commit)
via ba876505740d4782b58169848605af49325b11ed (commit)
via 4d505b0cef54209575173d52e4f9a3e6e244dab3 (commit)
via 6c611b5c04a5484df7269a603be4f00c4f7fa7f2 (commit)
via 0355bddc92f6df66ef50b920edd6ec3b27920d61 (commit)
via 62a61edffac3ebdd91fec693fe2c1a94785ddb25 (commit)
via 68b9571be6f2370798951b05968d07688e96d56d (commit)
via dec73bbd6dedb0068efe90ee8d77c020778b38db (commit)
via 6ff2a83cb7a6c04a258e84818257fe6eda9634a6 (commit)
via 682ea37cfb7e20366521fd48fc98826a21344942 (commit)
via e1e592789ebc8806b9a8767af95c41c1ec2d5a14 (commit)
via 4f42307f123f7ed147b537430e0f2e9a1c665e8e (commit)
via afa5624c2c3503df1f152f82278f1b9dba19b533 (commit)
via 88c0d241fe05e5ea91b10f046f307177cc2f5bc5 (commit)
via 3ceeab28d48c23ac561a2ed75a57a8f4aa153858 (commit)
via 888bcbc103826ce8de73b29c3e8bf48d5925fe27 (commit)
via ea679d6d1fc5105027de1f0243817c66a32fa8ac (commit)
via 510924ebc57def8085cc0e5413deda990b2abeee (commit)
via 1d6b96dd3f682a4433a02f2f156fd118aac6f0e0 (commit)
via 8d3f576ca98cdd6e821cf76464cd342d42d941a3 (commit)
via 13668e95ca6bbb07128babb14f1772bfefcb09a8 (commit)
via eddf905784249ab6949e6186f2676f9d627b089c (commit)
via b4111d2495797a6a9738df6dda3b9db696c5388b (commit)
via ece21d1bcb8b6ef6015622427cdc784e951b7396 (commit)
via 90946cbcad43d13bd6dec1a2a83eb75d019dd511 (commit)
via e28f5c6b01d30e48e8de45cb0312d360a44b7ca3 (commit)
via d151653e759da7ad45c28914a67988057e8cbd7f (commit)
via 9eb28db1b6b3c0b6cd76379416726b9931c729d2 (commit)
via 4a484575725500dd766516377eda41daaa17f402 (commit)
via ddfc6892916bc041e53b79a690abbed10ff99056 (commit)
via af0d35e501aab417b9b032069ae1cb8841debab4 (commit)
via cf5b24f110a927685226e8ef41378a847b002357 (commit)
via 5a7c1928eb5b70c2a9739650961bd4e32b6b26ed (commit)
via 238735e15837b8dcb878bbdb627ea4b61682db60 (commit)
via 782a56fbd225e31f6b1341857ad9a5af20df4901 (commit)
via f9a697ab99914d58e7e135ca069b3d5a06c00511 (commit)
via 3399d44c2d83f81b92a30bec360b3f207029bc3e (commit)
via 1516f8a92c2366f34a7c880ba6e6becad841e981 (commit)
via f6092c3b8c16a7155157ecedd3d5d9b4cd8dcfee (commit)
via 78986db7192134b31155c85e075e809acba2984c (commit)
via c77af8b7ddef2d9198b1a092e2a93aa6bc61b8d2 (commit)
via 2a4f9f2f6d0831520ddba84030873b70b9577c0c (commit)
via db4993d8478cad992ebfe8787edda08a6dd0d347 (commit)
via e099dc5e644156de47b9ad45e79a1732e2ba4314 (commit)
via ad723802c83996a3286fc5de23ef97c88ace004c (commit)
via 118edb29441d7299ef16cb752518680b4cbec503 (commit)
via 7b089ac66b9d15be53a0f874dfe4f2e47cb70c07 (commit)
via b5f4af26e1fffd82d4e9a9a2ba31ce9cd44f820e (commit)
via 1b02466ceefb1190fafd35c717dafc6285a557cd (commit)
via 012f9e78dc611c72ea213f9bd6743172e1a2ca20 (commit)
via c34be1b6a604ffb0a6ef34abfcf9c2fa42451d8c (commit)
via f2256e0e6f8260cf8addd1511fe49eaacf22d2bf (commit)
via d6c0273c617498a8bee3a813e013837e7c16b7e6 (commit)
via 681b1839bca185f19eca8e0fe567bb0012743a79 (commit)
via 99fdcbeaa5ea2a5432fc47f39c5ed40f4850b843 (commit)
via a263e7d8bc90224bb6acf8b096d6ce8c87c8647a (commit)
via b5c567f94b74e671986d68ae6e7549e829f72fa9 (commit)
via 320bafb306878cd9f13ba9703a0d4573fb645341 (commit)
via 1960b5becbba05570b9c7adf5129e64338659f07 (commit)
via 87f24fca3b55591ede24de9f856c807ca65966b1 (commit)
via f0b271b93312a990a566380815d70b758293934e (commit)
via 1e4827d9858483f0cb35ff90d3a665c08f9527d1 (commit)
via 3811dac0257939f28cefa621226019e6d1c75239 (commit)
via 8d638e07f2347d5e713493ffed9659ea5048e872 (commit)
via 0b53561c3b937c5d2585a3c62876b600800815bb (commit)
via e111d142cba584e410c5e385c10b714715f55dc4 (commit)
via 32d47188a5034c0ed3973099ebfe28ffe21ff1ce (commit)
via 61e7f493f8f44c7ccb8f2d9ef0186dd15e499097 (commit)
via 0c730e6357cf058473dc389820145d64cd5d946d (commit)
via e909ff99007b9b9b26af58848d3fccdb25d4135d (commit)
via 4dfa50d97a293f4ee50600805b903d8be761c9c3 (commit)
via ae9f367fb26b0fc24fdd5b3d43619fe2190201f8 (commit)
via 6a75877d0cd151127a80dece3ccbef19e6c672ee (commit)
via 31b0982e2b3a7c2f4a3cd31712a996aa3519944e (commit)
via 2754c99e705b7d45551aafe014fc2c11e6e4e6e3 (commit)
via 788d9505e0bec5369ccecc6f51af957874063fd8 (commit)
via eab1ff6c4b905655feb4aa76966e48e6112e3f63 (commit)
via cd1a6e393c28200adc827298ab4776f37e077af7 (commit)
via 32e4aaa9bdc0dc4e618960e0f6aa6c25105547c3 (commit)
via 451bbb67c2b5d544db2f7deca4315165245d2b3b (commit)
via 72de47d4fe6a2a65ac41efa998fe1b78a1616f1d (commit)
via 18ea57dd5bdcdd3e4e0dffef3f656597e4f06713 (commit)
via 6a1a198d0db14c0eceed4ce9ddb437f53cf74fbd (commit)
via 1165c559d56305469df3148f0a652bb1e2dcc9a1 (commit)
via ac453e66538072059954b8ca3e3d53bfc7c8cef3 (commit)
via dabcc74a7a5aa10a983d2e8bac2084e4ec5dadcb (commit)
via daf3c3de7e1ec602131dfb0d71df140b9b43093f (commit)
via b61a322c649b3766e45661551ab578275eec53cd (commit)
via e0122c1e3219488a152350666165369a0e9fbe32 (commit)
via f1f8de8ad231b05e58abbc8644d5c7ae5bfd9feb (commit)
via 6a34b5430a7e16c10cc08a957fedb46cd45b5bbf (commit)
via 21b1f9f943664a24ec0772c3ec7f07c63fa0c5fa (commit)
via 33d7934b9fda58090d00dd3994ca62eb280a5319 (commit)
via 6070acd1c5b2f7a61574eda4035b93b40aab3e2b (commit)
via fe7a9d0719b8ed30956d78f5564d8bd69ae3f9e3 (commit)
via 6672a4b36293b3959afbbd46accda352402d9436 (commit)
via 8da646ef1ffc59ac14234dc87882a8cf87b0e8dc (commit)
via 8190099591a5d3f4442f4235fee65a4a0800083a (commit)
via c416ba80303b3f6938dd93399ba988600e37c675 (commit)
via c65637dd41c8d94399bd3e3cee965b694b633339 (commit)
via 8609d278b5efdee7b218429063df1f6872ab2305 (commit)
via 2b0db8fee277900fdaf55cdf94c7365ead9f6f49 (commit)
via b677c094340db6ba6c37bba7b3e6b177830116f6 (commit)
via ddb07c03439bba577baf4f0c1462cbb8749d2d0e (commit)
via 34e8602a378948478f3f7275743db47b6857fb5a (commit)
via 8361a1340142b5465288fd7f126c92a4d04354b7 (commit)
via 47620f164b9541ce3c62437ff31efd1c134f3f05 (commit)
via ab4d484d2d17a0a2e3c4fc99453f7e314aee3b7f (commit)
via 34dbbd16060884ef96b20d28c110c5682db388ac (commit)
via 82f0bc8ecd6b3ad6c1419b4339ed3584351aa636 (commit)
via a32e082284a9afc697ac57ce02f76023d9402d1c (commit)
via d48aaf1aba81578c0e91609d97f83b83df93435e (commit)
via 2bb953ee477f97c9ed8b4f4dc3857ed0718f1aec (commit)
via 8251cd8f3a007756ad570dd7983660dfc353d6e2 (commit)
via 13a3fcfa3f8f256f5185faffededd36bcd03e5b5 (commit)
via f1bb311e77e15b5e43e2acd11e257bca11db4a7b (commit)
via 759761fd78c941bc3fa5332a8b4171ea3dad478a (commit)
via aba4c4067da0dc63c97c6356dc3137651755ffce (commit)
via 963acb307871f945870c61699cffee72de1c7b65 (commit)
via 3692ad59eab42084765ef68acea3eb24a9e327b1 (commit)
via 3cc446d3ed8a1b6c899ee19faeda2c41a4b5bdb2 (commit)
via 050c451f53da820f9b5ef47dcc469261a595d014 (commit)
via e79153fe56e7d514bd2ddf70450058e863c72d51 (commit)
via 5514dd78f2d61a222f3069fc94723ca33fb3200b (commit)
via cd620bf3e4315d693582f25538b3bb71941a42e5 (commit)
via 662e99ef050d98e86614c4443326568a0b5be437 (commit)
via 3b05e142949e5bee23b809625cb524e7e5ea66f8 (commit)
via 061e18bd967f3a456ec53b531590cffd12d0698e (commit)
via df2502bc7f9600f03dc410f01b1e6e060ea427ff (commit)
via 729bbdeb813e99a5f8323f29593f2aaadc95ce3f (commit)
via b923cbf809b74f03d3f13a147ac098dc376b45a4 (commit)
via f096d86833c3ec49b349d1dc0df91e84a8121891 (commit)
via 7a3dc628e96dd7b5201a8a1d3851992ea8d325ce (commit)
via ebba59e8684bf93f5239461d6c0724ac291243b5 (commit)
via 629023f290c290441129c96b11ece7de299fb8a6 (commit)
via 79e6083d6c946d9734337bd4ebb7f3a984fd6d05 (commit)
via 1e67f2cbd82e555241a366d5b93a7a6ec52c7921 (commit)
via 14d7b2bc8d2d3cbfbaf78d1d9a0492c4705aedd0 (commit)
via 8af77178bac36566bc64b0df7fe22a2b4494ea42 (commit)
via ce374384070155e16216b2624bd89f184993df0d (commit)
via ba9ed9d67285288e425e9fe9ebddf99bcda69d8a (commit)
via 0ec520e6070de64a0462e3239e1be48aa8635ffd (commit)
via 36208de9b48fdf61061554819f72a72258589f84 (commit)
via 1d88daaa24e8b1ab27f28be876f40a144241e93b (commit)
via 4049550671160e2bbc06ca7e65996d0b7e243971 (commit)
via a5c11800c36942b37a3b69f48513ed3fcb31fb46 (commit)
via da6a8d0c07e20f0b258d774de436b8d363c81bd8 (commit)
via 728f0af26952a8e684be96c73567defbe81052ab (commit)
via 9ea1252c6694bd933049e0629cae3d2d4a1cd47e (commit)
via f8fb852bc6aef292555063590c361f01cf29e5ca (commit)
via a92158cc1d674e0dce9e75acbb40a1a9bd807f0e (commit)
via 3a4e135e22cb4e986ca4c1902d34172ccc9aeae1 (commit)
via 314f9f7615f42a76fe8e741aac266cca8b988807 (commit)
via b7d0dbbcb1f1cc69efe9630afe07037bdafe1d09 (commit)
via 674efef18dbc1a215af65b31b4b38e33f53a6387 (commit)
via 66e6ecea1445b3fb151adda962db171ee65b0501 (commit)
via f1f8b24ea7bd7d408fd009e858458722508db0e7 (commit)
via af4b7eba43ad36c0510c77710d975a9beeb08f89 (commit)
via 659d03c759ca8d350c6c8b7bb6583b09dafa0f54 (commit)
via fdfe3422ffc3d7f6eb44114d02a0a3759d4dee7c (commit)
via 721a53160c15e8218f6798309befe940b9597ba0 (commit)
via 677c3d840612c58d1cd011adb678e464a904f959 (commit)
via 67bfb6c60061a86dd0420df77d5cb85af935c634 (commit)
via ec8977f28f7824c8c0b07180eda806be22110199 (commit)
via 2e697f95732f976f3ba51a671a0c23d2057f3162 (commit)
via e2c01932a3a51d44a76cdfe7c97bcc14bab8a1cc (commit)
via 19a98f3b7abb7ec47957f9143628174f61e3f590 (commit)
via 76022a7e9f3ff339f0f9f10049aa85e5784d72c5 (commit)
via c952ddadcd1c0339097009c7cc9015bf33fb81aa (commit)
via dd4e05070a8868a7f42eab9096f0b4d506bbc027 (commit)
via 170485efd4eb6cb6ba6af78d62be71b2354befd8 (commit)
via 5874ea28a5e9a3af38fc15c53bcf9d0690680e6c (commit)
via 4fd1b4c0f03f01b2fb085dae50455f6746b435c9 (commit)
via 691a4a2f4e82d52d313e67ddd9b1e5480226fefe (commit)
via 52247fe2ac1e071d95b3333d3c0cea73a0429399 (commit)
via 454739d105be01d7b9711038ca68271d0f4fd02c (commit)
via 065327bbba5925b882af5230e5d312750e619a91 (commit)
via a72483929eb91cf20d41ca5c40bd6ba8b6f31e60 (commit)
via 3e6c2a9c09722a48fe3e6c8a1ee35724a9397360 (commit)
via 0f225ec3b92e320020b731720e8ebe4d5d1fadf8 (commit)
via de3c708f962db8fe15287c9a5704d3aa8b257b03 (commit)
via 9b90408108142c710c2adb403e61af374fe1b24c (commit)
via 00cb2ff77044b185fe80dcf3f577a9b4793638b0 (commit)
via 00b37501ae500c81657b1727dfff0105c2202f07 (commit)
via 7b794844b6afc77cf05bb93828477fe5cc48408d (commit)
via 75b29aaa4aabf843b0d80400eb2cf8c39c4dd8f0 (commit)
via 11889ab357ea40764683e4be03bcc27a4ab6bb04 (commit)
via 9efbe64fe3ff22bb5fba46de409ae058f199c8a7 (commit)
via 7e385db1cb3baae0bcc5461c17a07062816cc5a6 (commit)
via 3487cf43d093e5f3abc120fb416a73d8a0e6a878 (commit)
via 527718a5b0447981ab410c5379bb1a68ef4d9fa9 (commit)
via fef3266de36bdf6b6f349d3176ce37b6e29f0a7e (commit)
via db1c1a4eec4249d96c385c69a4fe4bc8c49b1b10 (commit)
via dcaab4f4a892d1ab0e9e2c246282066891b2ffcb (commit)
via 385a9f81dd2d30a370b2f45a6ff0ec7d09dbea79 (commit)
via 420ec42bd913fb83da37b26b75faae49c7957c46 (commit)
via acbd9518519498fcf9d476b5417bcbbb2b9141be (commit)
via 1ecb969fc160370724527c3e2e1812463f2398ca (commit)
via 4c05048ba059b79efeab53498737abe94d37ee07 (commit)
via bf0b8426e2d05c1fc30a2b29929531c430f8bcda (commit)
via 02d45b17f160bd3662ee765147debe770c6d3faa (commit)
via fc26c7396d98fa84a8f057cc409303f911792365 (commit)
via ba92da6356b8d3a5b164102af9d8d22259efccc9 (commit)
via 7c38ee8fabb2f93c83495e45c58a37712da25e67 (commit)
via b1bc4d63d568d15f6b21a3c25a2848bea2553ff7 (commit)
via a0d91794e105108425429a6ab9eed196171955eb (commit)
via db57bdd2f3f73b7d4048fec614f0a3db73c8489a (commit)
via cf2b8ace9b9c295e46fdd5373bd70a13e7e3fbee (commit)
via cbc69359f3d9841a217ee55f0e30937a86cb0097 (commit)
via 87d58087b71b509d9798bd700b9ba968712e142d (commit)
via 827babb0d6caade8ca685e5f3e779c756f9ebe1a (commit)
via 1b24dfdd078a3eddf6198d9561ae8bb93803103d (commit)
via 45b76ba773367f8a490bbf19f0d74c02293c387b (commit)
via f009a637580c569a49a74b87a384a242c5a5f30a (commit)
via 002aa60f46e33079f4d9bc131115a127ea5a5130 (commit)
via 3a6535a46295133690647d8fb75819abb5107bac (commit)
via 9dcb53261abe3c16324155fbc4c3436100b9e2ee (commit)
via 41ef6df597d8831d080d4dfc7c582f06e7f92511 (commit)
via 5b825a61a8f083aa879da6aedc6717d42534d5b4 (commit)
via 933b35dcb9cec5113853c93994d69cac44a97f4b (commit)
via e1b0deba4ea46e097e15f40b9825f7abffa00596 (commit)
via 5ecd1322b677fa9808125986a65b28d619c79120 (commit)
via 78828990b58d0b791f94ed2441bf1267f81d58d3 (commit)
via e33f9a6d1009753b2f02b43e43da1014b0df1964 (commit)
via 07c01a7a90476fd6b90659bf809e7135ce26cff2 (commit)
via a1d7f70e7c660e847fb388c6f17d6709c93fe8e2 (commit)
via 96e55aef179874427ea28641316dd145fbd98175 (commit)
via ccdce3bab3f04bade1e92e66e3929ffd0db97f0b (commit)
via 79155a51eb422813900332ad1582ed5964d70f5c (commit)
via bf57a576ff80578d47471569a48f0fbfd1fb6e2e (commit)
via d27da40718b797aca37ad5bab7215592a089627a (commit)
via 9d42d008eb9ab1a9f271c7c20754ba02785772f0 (commit)
via 117fa08fc7f8b7462190b75f99d7604c49f3b7c6 (commit)
via 447c40bcee5edbd71ee1b3812fa71727b2fdc7a6 (commit)
via f1a6ba985d60a31bb5cffe0571c827c3f7b25ce3 (commit)
via f1396eba9c5c5b1291ba24e372a0b536e3deb1c0 (commit)
via 0b42ead35810e82a97561d71309283950ca9d5a3 (commit)
via a73a3ef00b42b0fc5aa5ecf832cb713db74288a1 (commit)
via 610400fa75aa44d15fdc95dc8b69e02b45698765 (commit)
via cf020fa451a20df9625253b3b2ccbc00e8144c1c (commit)
via 6c1f3af003f02eee8952da8463d05732e667c986 (commit)
via be4c4ffc719d10735bfaea5a7cd5bdea7e5f06c7 (commit)
via 715fd606c4d77f1947c8c26142a5d516d54a00da (commit)
via d686e3ada2141094413e756d601d2e727fb6f760 (commit)
via d0dcd91d39a438d5a18e0726251e1a93212143ca (commit)
via b32ea06f28de94a8ec779385d5e86374e81c023d (commit)
via f73c46eb406d7234dd7519a1d330db67813b31ab (commit)
via 1faf02bda80053ddb2f815466d0372a2eeb6c08c (commit)
via 9e782794969ab034eb92dca6a8d5ee8f518ccc95 (commit)
via 1f682b8172e368d7609aa2f066b778989a7fdf7e (commit)
via 87308eeddf767dea581b817d1d2ab2aaa4a99dd3 (commit)
via c5ce94c579d2d022cc7a39f826599c12c193dc20 (commit)
via 6b5705bb7f6fac495f8b3e050ce9089997416ccb (commit)
via e9cd09d4a41cfb46af3a89e57f7d3184c602dc06 (commit)
via 8c136625d1e2556c7c8280917a1f18370794ce76 (commit)
via af0eb33e4d57c842f692b653d783e028250264dc (commit)
via 6ed6d55c1e9908ce49d4ba2584c9d647c23908ba (commit)
via ca06de9c9e5a017361041a7ce0db4bd37c27e0a7 (commit)
via 0e52d28e09894ae1cab993e9cf61a49d00f9be64 (commit)
via f9efa6910cdf152f850a76c039f597f516f0c2ce (commit)
via c0cec0792078fbdd5a6cc5cb19c4361a960d95cf (commit)
via 6ba7a9cf0d9bcaa52ab17652ef1e6e3ad78094a0 (commit)
via f664bc1482edb6da965169317f04f2d7b6458fbd (commit)
via e241028cc0993fb87306bd097a45f99a5ea3a50d (commit)
via e1db14abce5e64985340e4ebb9eb4e7bee56763e (commit)
via 6483bb374e7ac88e3b736f08bf11e292605aaeef (commit)
via a3d81c1479c88a8ba26941e351fce1b0b1e3a2ca (commit)
via 895e8df77e0d3f8957f3b518052ff874f67bb8b8 (commit)
via cb13c22e8b2464d903d2254dec4d46c1103cf5ea (commit)
via 68c33a7725a2e41dab53b4d74e2aedec79090380 (commit)
via 860be372d776c05c9535790257812dbde1b9f74f (commit)
via b73ea473019bceeab7aab78ddc5d1c4a7710a8bd (commit)
via 70d71681b83dd3e0cb125e3f3e176d04e7461aae (commit)
via 21d48af75c1e756de9acea4c45dc35634c0475ac (commit)
via 9ae710aa95a75d63e2a37ca7d79297f8ef308e6c (commit)
via cac094e7e14a483bbf84394adf55a86d70097188 (commit)
via 5017cbe7d18c752b80e8e8289b9ab0315f5915d0 (commit)
via 2e967b7775e023f77f082f49457342fff2c7be33 (commit)
via 77027490d53b644e157fce2df59c5dbd496d1e1a (commit)
via be701769ab3ba884c550d22455ba09af3b725a07 (commit)
via f4b20fc54b74d03279245942900806a5e7b57d39 (commit)
via 38f2d6e49c7f693c55e5d27b3b247a167895826c (commit)
via 120a7ea6efb5ba35008ed9b3502846f4b8fb2ed8 (commit)
via 682436e844799b2194eccc2ffdfb3fa3da527cea (commit)
via 28c720f2b0319ee8b2ee21cea1105e411a31360c (commit)
via d82bd9a601e95a301e268c21a8ddcfea560d38dc (commit)
via 3cb71c8c163525c612460eff4292adb997f8a797 (commit)
via 2a0f21d3415558e8be812e74e554e11c6cbd6270 (commit)
via b13c2fd090114e2c0cfe164bae10cde145f509b4 (commit)
via 19603be4d345c061cb2345187cfed58a785bf03a (commit)
via cc7a7fb930a5300aae369665ddc882c8bbf73cb7 (commit)
via 7c419681601df9c3a453f0e46756dd751344b1a8 (commit)
via f0dd8824dc9bb06420b960f2842902ca2d63fba3 (commit)
via af01e66fde8c0055319c47d775b0af1408a3e18a (commit)
via 7362cbdfc230a2f8deee2933b78e23bdb4892e98 (commit)
via 48846bf7b70cc48e317aabc554a4dacef89b2d46 (commit)
via d4af3712f3987069dffe6ed30919ce0b7bf84699 (commit)
via ea0d42e325de86353e17b29c26257333f0fe016e (commit)
via 094d2aa6c73201892be2362d6d43fdeb34a2650f (commit)
via 66174ced68123b63ec6d23a5f4ecd17302fd2e8b (commit)
via ef6da9e4e8d00024a6c7565eecc6216331ddfee1 (commit)
via dd1809bf5b3c9b6ac372523f5fc96a1baaf7d0a5 (commit)
via 70c1df71874b71443129d7205297ae5ed630a83d (commit)
via 00e1d85817e9304e1836bbf2f2e7eb194e4617c4 (commit)
via 5f59f72eeb28488a1a4e4c336b33fccc2215ba06 (commit)
via 501462d7b9850ddca6a3d0a2c7043e3fa0923759 (commit)
via e321d8b344152b558cb42856deb5e8798f153d1b (commit)
via 044c080dd3fc084f3ee7dcdcc70c8dc328bee9df (commit)
via 1cd8ba5e307c77ae9bc2563569366c43623bbc75 (commit)
via 74d131f92fbcd1fe21335a743dde205063b63d00 (commit)
via 8599f0c0c2ebe7043dd65458a2302837f7c43895 (commit)
via 902005dceae646aefc03c2005e1ff2896efb909c (commit)
via 8a18de79026a2620f82662d308c4f87e9a54faa3 (commit)
via fa743a8d8303433152e7c93bfc8aca1a2aa9cf0e (commit)
via 4434d7bef7a127ad8fb7e9dc6a43c9bd9e6601d1 (commit)
via c7a0fffa73e28d0d28b3706a9bc6b6e1ed9d2a7b (commit)
via 344410fec60755f6b5cb2348ec9e7b67068f31ae (commit)
via e88330e1dbfed90776b634513372f71caa96ac5f (commit)
via 6f9b0595718f8f7fd5324a6252207d29c40836b2 (commit)
via d11a387719a4ea69946801e4c865323f4b9016a3 (commit)
via 88d4219ba95fa853b07c3397aeb4c2e6567e50c0 (commit)
via 8da3cbf0ec4fcd563b8e779038a38811a77dece8 (commit)
via 66bb0f76db68b8f96fc4ac819f9c07c7f8a404e6 (commit)
via 26b876a067e285be526793254f9422067a89e071 (commit)
via be4ec3ec23084ca94e72a60ddb1a2d4250ade5cd (commit)
via d58451bac9dd51a562a24032d013e1eacc5a3fde (commit)
via 6b09fa3b6e19b73f82940664802158baaca0297d (commit)
via 1f7ffbad521f059f611876c27d8373f355ca8c40 (commit)
via 0add6c7b1047219eda3f1d7fa13340da61cd96b5 (commit)
via 3b1fef1e85522e061114beb6bd9013a51da16c08 (commit)
via 3772748aa192829099877ac1c1e43e43b691df16 (commit)
via 7dab055fe9854655b43338485ec8a960dea94080 (commit)
via 78533f09dbf43b91578392dc8cee6700b3db7d2b (commit)
via 5d8d976fe843af90f06dc9ff48408e96a51a0316 (commit)
from ebc01eeac4a9ab34ef73e984a3d16e9c59ccd42b (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 91bf551afc20173718fdbbd49883b07ab1bc0e04
Merge: ebc01eeac4a9ab34ef73e984a3d16e9c59ccd42b 047cdc39aa335b778d4ba9ecdacbd859d612807c
Author: zhanglikun <zhanglikun at cnnic.cn>
Date: Thu Apr 14 19:57:20 2011 +0800
[trac598] Merge master to trac598
-----------------------------------------------------------------------
Summary of changes:
ChangeLog | 567 ++++++++-----
INSTALL | 4 +-
README | 6 +-
configure.ac | 13 +-
doc/Doxyfile | 6 +-
doc/guide/bind10-guide.html | 76 +-
doc/guide/bind10-guide.xml | 54 +-
ext/asio/asio/detail/epoll_reactor.hpp | 2 +-
ext/asio/asio/detail/kqueue_reactor.hpp | 2 +-
ext/asio/asio/detail/null_thread.hpp | 2 +-
src/bin/Makefile.am | 2 +-
src/bin/auth/Makefile.am | 4 +-
src/bin/auth/auth_config.cc | 347 +++++++
src/bin/auth/{config.h => auth_config.h} | 0
src/bin/auth/auth_srv.cc | 2 +-
src/bin/auth/auth_srv.h | 9 +
src/bin/auth/benchmarks/Makefile.am | 2 +-
src/bin/auth/benchmarks/query_bench.cc | 4 +-
src/bin/auth/common.cc | 36 +
src/bin/auth/common.h | 11 +-
src/bin/auth/config.cc | 347 -------
src/bin/auth/main.cc | 28 +-
src/bin/auth/query.cc | 2 +
src/bin/auth/tests/Makefile.am | 8 +-
src/bin/auth/tests/command_unittest.cc | 4 +-
src/bin/auth/tests/common_unittest.cc | 96 ++
src/bin/auth/tests/config_unittest.cc | 2 +-
src/bin/auth/tests/query_unittest.cc | 12 +-
src/bin/bind10/bind10.8 | 10 +-
src/bin/bind10/bind10.py.in | 166 +++-
src/bin/bind10/bind10.xml | 30 +
src/bin/bind10/bob.spec | 15 +
src/bin/bind10/tests/Makefile.am | 4 +-
src/bin/bind10/tests/bind10_test.in | 32 -
src/bin/bind10/tests/bind10_test.py.in | 248 +++++-
src/bin/bindctl/bindcmd.py | 18 +-
src/bin/bindctl/bindctl.1 | 15 +-
src/bin/bindctl/tests/bindctl_test.py | 108 ++-
src/bin/cfgmgr/b10-cfgmgr.8 | 18 +-
src/bin/cfgmgr/b10-cfgmgr.py.in | 19 +-
src/bin/cfgmgr/b10-cfgmgr.xml | 48 +-
src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in | 66 ++-
src/bin/cmdctl/cmdctl.py.in | 1 -
src/bin/msgq/tests/msgq_test.py | 4 +-
src/bin/resolver/Makefile.am | 1 +
src/bin/resolver/main.cc | 57 ++-
src/bin/resolver/resolver.cc | 44 +-
src/bin/resolver/resolver.h | 42 +-
src/bin/resolver/response_scrubber.h | 4 +-
src/bin/resolver/tests/Makefile.am | 1 +
src/bin/resolver/tests/resolver_config_unittest.cc | 3 +
src/bin/sockcreator/Makefile.am | 18 +
src/bin/sockcreator/README | 49 +
src/bin/sockcreator/main.cc | 26 +
src/bin/sockcreator/sockcreator.cc | 151 ++++
src/bin/sockcreator/sockcreator.h | 100 ++
src/bin/sockcreator/tests/Makefile.am | 25 +
src/bin/sockcreator/tests/run_unittests.cc | 22 +
src/bin/sockcreator/tests/sockcreator_tests.cc | 273 ++++++
src/bin/stats/b10-stats.8 | 19 +-
src/bin/stats/b10-stats.xml | 6 +-
src/bin/stats/stats.py.in | 12 +-
src/bin/stats/tests/b10-stats_test.py | 11 +-
src/bin/tests/process_rename_test.py.in | 2 +-
src/bin/xfrin/xfrin.py.in | 1 -
src/bin/xfrout/tests/Makefile.am | 4 +-
src/bin/xfrout/tests/xfrout_test.py | 437 ---------
src/bin/xfrout/tests/xfrout_test.py.in | 469 ++++++++++
src/bin/xfrout/xfrout.py.in | 42 +-
src/bin/zonemgr/tests/zonemgr_test.py | 132 ++-
src/bin/zonemgr/zonemgr.py.in | 73 +-
src/bin/zonemgr/zonemgr.spec.pre.in | 26 +
src/lib/Makefile.am | 4 +-
src/lib/asiolink/Makefile.am | 28 +-
src/lib/asiolink/asiodef.cc | 37 +
src/lib/asiolink/asiodef.h | 21 +
src/lib/asiolink/asiodef.msg | 56 ++
src/lib/asiolink/asiolink.h | 5 -
src/lib/asiolink/asiolink_utilities.h | 61 ++
src/lib/asiolink/dns_lookup.h | 4 +-
src/lib/asiolink/dns_server.h | 10 +-
src/lib/asiolink/dns_service.h | 6 +-
src/lib/asiolink/dummy_io_cb.h | 8 +
src/lib/asiolink/internal/iofetch.h | 125 ---
src/lib/asiolink/interval_timer.h | 2 +-
src/lib/asiolink/io_address.h | 6 +-
src/lib/asiolink/io_asio_socket.h | 214 ++++--
src/lib/asiolink/io_endpoint.cc | 14 +
src/lib/asiolink/io_endpoint.h | 7 +-
src/lib/asiolink/io_fetch.cc | 323 +++++--
src/lib/asiolink/io_fetch.h | 152 ++--
src/lib/asiolink/io_message.h | 4 -
src/lib/asiolink/io_socket.cc | 2 -
src/lib/asiolink/recursive_query.h | 128 ---
src/lib/asiolink/tcp_endpoint.h | 61 +-
src/lib/asiolink/tcp_server.cc | 36 +-
src/lib/asiolink/tcp_server.h | 3 -
src/lib/asiolink/tcp_socket.h | 301 +++++--
src/lib/asiolink/tests/Makefile.am | 13 +-
.../asiolink/tests/asiolink_utilities_unittest.cc | 74 ++
src/lib/asiolink/tests/dns_server_unittest.cc | 501 +++++++++++
src/lib/asiolink/tests/io_endpoint_unittest.cc | 56 ++
src/lib/asiolink/tests/io_fetch_unittest.cc | 757 +++++++++++++---
src/lib/asiolink/tests/recursive_query_unittest.cc | 808 -----------------
src/lib/asiolink/tests/run_unittests.cc | 6 +-
src/lib/asiolink/tests/tcp_endpoint_unittest.cc | 55 ++
src/lib/asiolink/tests/tcp_socket_unittest.cc | 515 +++++++++++
src/lib/asiolink/tests/udp_socket_unittest.cc | 88 ++-
src/lib/asiolink/udp_endpoint.h | 11 +
src/lib/asiolink/udp_server.cc | 27 +-
src/lib/asiolink/udp_socket.h | 184 +++--
src/lib/cache/Makefile.am | 1 +
src/lib/cache/TODO | 4 +
src/lib/cache/message_cache.cc | 41 +-
src/lib/cache/message_cache.h | 18 +-
src/lib/cache/message_entry.cc | 89 ++-
src/lib/cache/message_entry.h | 69 +-
src/lib/cache/message_utility.cc | 80 ++
src/lib/cache/message_utility.h | 66 ++
src/lib/cache/resolver_cache.cc | 27 +-
src/lib/cache/resolver_cache.h | 23 +-
src/lib/cache/rrset_cache.cc | 48 +-
src/lib/cache/rrset_cache.h | 6 +-
src/lib/cache/tests/Makefile.am | 14 +-
src/lib/cache/tests/message_cache_unittest.cc | 56 +-
src/lib/cache/tests/message_entry_unittest.cc | 64 +-
src/lib/cache/tests/negative_cache_unittest.cc | 242 +++++
src/lib/cache/tests/resolver_cache_unittest.cc | 2 +-
src/lib/cache/tests/rrset_cache_unittest.cc | 100 ++-
.../tests/testdata/message_cname_referral.wire | 56 ++
.../tests/testdata/message_example_com_soa.wire | 57 ++
src/lib/cache/tests/testdata/message_fromWire9 | 25 +
.../cache/tests/testdata/message_large_ttl.wire | 31 +
.../tests/testdata/message_nodata_with_soa.wire | 32 +
.../tests/testdata/message_nxdomain_cname.wire | 36 +
.../tests/testdata/message_nxdomain_large_ttl.wire | 25 +
.../tests/testdata/message_nxdomain_no_soa.wire | 26 +
.../tests/testdata/message_nxdomain_with_soa.wire | 55 ++
src/lib/cache/tests/testdata/message_referral.wire | 36 +
src/lib/cc/data.h | 12 +-
src/lib/cc/session.h | 2 +-
src/lib/config/module_spec.cc | 19 +-
src/lib/config/module_spec.h | 4 +
src/lib/config/tests/module_spec_unittests.cc | 4 +
src/lib/config/tests/testdata/Makefile.am | 1 +
src/lib/config/tests/testdata/data22_10.data | 11 +
src/lib/datasrc/data_source.cc | 69 ++-
src/lib/datasrc/memory_datasrc.h | 2 +-
src/lib/datasrc/rbtree.h | 23 +-
src/lib/datasrc/result.h | 1 -
src/lib/datasrc/tests/Makefile.am | 1 +
src/lib/datasrc/tests/datasrc_unittest.cc | 343 +++++---
src/lib/datasrc/tests/memory_datasrc_unittest.cc | 1 -
src/lib/datasrc/tests/rbtree_unittest.cc | 54 +-
src/lib/datasrc/tests/test_datasrc.cc | 34 +-
src/lib/datasrc/zone.h | 1 -
src/lib/datasrc/zonetable.h | 2 +-
src/lib/dns/buffer.h | 132 +++-
src/lib/dns/edns.h | 4 +-
src/lib/dns/masterload.h | 6 +-
src/lib/dns/message.h | 12 +-
src/lib/dns/python/edns_python.cc | 11 +-
src/lib/dns/python/message_python.cc | 35 +-
src/lib/dns/python/messagerenderer_python.cc | 16 +-
src/lib/dns/python/name_python.cc | 54 +-
src/lib/dns/python/question_python.cc | 2 +-
src/lib/dns/python/rcode_python.cc | 10 +-
src/lib/dns/python/rrclass_python.cc | 11 +-
src/lib/dns/python/rrttl_python.cc | 8 +-
src/lib/dns/python/rrtype_python.cc | 12 +-
src/lib/dns/python/tests/edns_python_test.py | 13 +-
src/lib/dns/python/tests/message_python_test.py | 28 +-
.../python/tests/messagerenderer_python_test.py | 5 +
src/lib/dns/python/tests/name_python_test.py | 19 +-
src/lib/dns/python/tests/question_python_test.py | 2 +-
src/lib/dns/python/tests/rcode_python_test.py | 19 +-
src/lib/dns/python/tests/rrclass_python_test.py | 9 +-
src/lib/dns/python/tests/rrttl_python_test.py | 10 +-
src/lib/dns/python/tests/rrtype_python_test.py | 16 +-
src/lib/dns/question.h | 10 +-
src/lib/dns/rdata.cc | 3 +-
src/lib/dns/rdata.h | 11 +
src/lib/dns/rdata/generic/rp_17.cc | 124 +++
src/lib/dns/rdata/generic/rp_17.h | 88 ++
src/lib/dns/rdata/template.cc | 2 +-
src/lib/dns/rdata/template.h | 2 +-
src/lib/dns/rrset.h | 2 -
src/lib/dns/rrttl.h | 6 +-
src/lib/dns/tests/Makefile.am | 1 +
src/lib/dns/tests/buffer_unittest.cc | 55 ++-
src/lib/dns/tests/rdata_rp_unittest.cc | 162 ++++
src/lib/dns/tests/testdata/Makefile.am | 8 +
src/lib/dns/tests/testdata/gen-wiredata.py.in | 35 +-
src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec | 6 +
src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec | 12 +
src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec | 7 +
src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec | 7 +
src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec | 7 +
src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec | 7 +
src/lib/dns/tests/testdata/rdata_rp_toWire1.spec | 8 +
src/lib/dns/tests/testdata/rdata_rp_toWire2.spec | 14 +
src/lib/log/Makefile.am | 7 +-
src/lib/log/README | 4 +-
src/lib/log/dummylog.cc | 2 +-
src/lib/log/dummylog.h | 7 +-
src/lib/log/filename.h | 2 +-
src/lib/log/logger.h | 2 +-
src/lib/log/logger_support.cc | 2 +-
src/lib/log/logger_support.h | 2 +-
src/lib/log/message_dictionary.cc | 2 +-
src/lib/log/message_dictionary.h | 2 +-
src/lib/log/message_reader.cc | 2 +-
src/lib/log/messagedef.mes | 10 +-
src/lib/log/strutil.h | 4 +-
src/lib/log/xdebuglevel.h | 4 +-
src/lib/nsas/Makefile.am | 1 +
src/lib/nsas/address_entry.h | 4 +-
src/lib/nsas/asiolink.h | 37 -
src/lib/nsas/fetchable.h | 2 +-
src/lib/nsas/glue_hints.cc | 168 ++++
src/lib/nsas/glue_hints.h | 71 ++
src/lib/nsas/hash.h | 2 +-
src/lib/nsas/hash_table.h | 2 +-
src/lib/nsas/locks.h | 5 -
src/lib/nsas/lru_list.h | 28 +-
src/lib/nsas/nameserver_address.cc | 6 +-
src/lib/nsas/nameserver_address.h | 4 +-
src/lib/nsas/nameserver_address_store.cc | 33 +-
src/lib/nsas/nameserver_address_store.h | 14 +-
src/lib/nsas/nameserver_entry.cc | 18 +-
src/lib/nsas/nameserver_entry.h | 6 +-
src/lib/nsas/random_number_generator.h | 73 ++-
src/lib/nsas/tests/Makefile.am | 1 +
src/lib/nsas/tests/address_entry_unittest.cc | 4 +-
src/lib/nsas/tests/fetchable_unittest.cc | 2 +-
src/lib/nsas/tests/lru_list_unittest.cc | 29 +
.../tests/nameserver_address_store_unittest.cc | 40 +-
src/lib/nsas/tests/nameserver_address_unittest.cc | 14 +-
src/lib/nsas/tests/nameserver_entry_unittest.cc | 34 +-
src/lib/nsas/tests/nsas_test.h | 20 +-
.../nsas/tests/random_number_generator_unittest.cc | 16 +-
src/lib/nsas/tests/zone_entry_unittest.cc | 42 +-
src/lib/nsas/zone_entry.cc | 39 +-
src/lib/nsas/zone_entry.h | 21 +-
src/lib/python/isc/Makefile.am | 2 +-
src/lib/python/isc/config/ccsession.py | 4 +-
src/lib/python/isc/config/cfgmgr.py | 66 +-
src/lib/python/isc/config/config_data.py | 4 +-
src/lib/python/isc/config/tests/ccsession_test.py | 13 +-
src/lib/python/isc/config/tests/cfgmgr_test.py | 40 +-
.../python/isc/config/tests/config_data_test.py | 4 +-
.../isc/config/tests/unittest_fakesession.py | 9 +-
src/lib/python/isc/net/parse.py | 2 +-
src/lib/python/isc/net/tests/parse_test.py | 2 +-
src/lib/python/isc/testutils/Makefile.am | 1 +
src/lib/python/isc/testutils/README | 3 +
src/lib/python/isc/testutils/__init__.py | 17 +
src/lib/python/isc/testutils/parse_args.py | 30 +
src/lib/python/isc/util/process.py | 2 +-
src/lib/python/isc/util/tests/process_test.py | 2 +-
src/lib/rbmsgq/lib/cc.rb | 60 --
src/lib/rbmsgq/lib/cc/message.rb | 324 -------
src/lib/rbmsgq/lib/cc/session.rb | 214 -----
src/lib/resolve/Makefile.am | 11 +
src/lib/resolve/recursive_query.cc | 944 ++++++++++++++++++++
src/lib/resolve/recursive_query.h | 144 +++
src/lib/resolve/resolver_callback.h | 4 +-
src/lib/resolve/resolver_interface.h | 2 +-
src/lib/resolve/response_classifier.cc | 10 +-
src/lib/resolve/response_classifier.h | 1 +
src/lib/resolve/tests/Makefile.am | 8 +
src/lib/resolve/tests/recursive_query_unittest.cc | 873 ++++++++++++++++++
.../resolve/tests/recursive_query_unittest_2.cc | 677 ++++++++++++++
.../resolve/tests/resolver_callback_unittest.cc | 2 +-
.../resolve/tests/response_classifier_unittest.cc | 17 +
src/lib/server_common/tests/Makefile.am | 1 +
src/lib/testutils/Makefile.am | 1 +
src/lib/testutils/srv_test.cc | 2 +
src/lib/util/Makefile.am | 3 +
src/lib/util/io/Makefile.am | 16 +
src/lib/util/io/fd.cc | 70 ++
src/lib/util/io/fd.h | 61 ++
src/lib/util/io/fd_share.cc | 139 +++
src/lib/util/io/fd_share.h | 65 ++
src/lib/util/io/fdshare_python.cc | 97 ++
src/lib/util/io/tests/Makefile.am | 25 +
src/lib/util/io/tests/fd_share_tests.cc | 74 ++
src/lib/util/io/tests/fd_tests.cc | 66 ++
src/lib/util/io/tests/run_unittests.cc | 22 +
src/lib/util/unittests/Makefile.am | 9 +
src/lib/util/unittests/README | 5 +
src/lib/util/unittests/fork.cc | 145 +++
src/lib/util/unittests/fork.h | 52 ++
src/lib/xfr/Makefile.am | 14 +-
src/lib/xfr/fd_share.cc | 137 ---
src/lib/xfr/fd_share.h | 42 -
src/lib/xfr/fdshare_python.cc | 84 --
src/lib/xfr/python_xfr.cc | 32 -
src/lib/xfr/xfrout_client.cc | 5 +-
tests/system/bindctl/tests.sh | 4 +-
300 files changed, 12872 insertions(+), 4716 deletions(-)
create mode 100644 src/bin/auth/auth_config.cc
rename src/bin/auth/{config.h => auth_config.h} (100%)
create mode 100644 src/bin/auth/common.cc
delete mode 100644 src/bin/auth/config.cc
create mode 100644 src/bin/auth/tests/common_unittest.cc
delete mode 100755 src/bin/bind10/tests/bind10_test.in
create mode 100644 src/bin/sockcreator/Makefile.am
create mode 100644 src/bin/sockcreator/README
create mode 100644 src/bin/sockcreator/main.cc
create mode 100644 src/bin/sockcreator/sockcreator.cc
create mode 100644 src/bin/sockcreator/sockcreator.h
create mode 100644 src/bin/sockcreator/tests/Makefile.am
create mode 100644 src/bin/sockcreator/tests/run_unittests.cc
create mode 100644 src/bin/sockcreator/tests/sockcreator_tests.cc
delete mode 100644 src/bin/xfrout/tests/xfrout_test.py
create mode 100644 src/bin/xfrout/tests/xfrout_test.py.in
create mode 100644 src/lib/asiolink/asiodef.cc
create mode 100644 src/lib/asiolink/asiodef.h
create mode 100644 src/lib/asiolink/asiodef.msg
create mode 100644 src/lib/asiolink/asiolink_utilities.h
delete mode 100644 src/lib/asiolink/internal/iofetch.h
delete mode 100644 src/lib/asiolink/recursive_query.h
create mode 100644 src/lib/asiolink/tests/asiolink_utilities_unittest.cc
create mode 100644 src/lib/asiolink/tests/dns_server_unittest.cc
delete mode 100644 src/lib/asiolink/tests/recursive_query_unittest.cc
create mode 100644 src/lib/asiolink/tests/tcp_endpoint_unittest.cc
create mode 100644 src/lib/asiolink/tests/tcp_socket_unittest.cc
create mode 100644 src/lib/cache/message_utility.cc
create mode 100644 src/lib/cache/message_utility.h
create mode 100644 src/lib/cache/tests/negative_cache_unittest.cc
create mode 100644 src/lib/cache/tests/testdata/message_cname_referral.wire
create mode 100644 src/lib/cache/tests/testdata/message_example_com_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_fromWire9
create mode 100644 src/lib/cache/tests/testdata/message_large_ttl.wire
create mode 100644 src/lib/cache/tests/testdata/message_nodata_with_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_cname.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
create mode 100644 src/lib/cache/tests/testdata/message_referral.wire
create mode 100644 src/lib/config/tests/testdata/data22_10.data
create mode 100644 src/lib/dns/rdata/generic/rp_17.cc
create mode 100644 src/lib/dns/rdata/generic/rp_17.h
create mode 100644 src/lib/dns/tests/rdata_rp_unittest.cc
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_toWire1.spec
create mode 100644 src/lib/dns/tests/testdata/rdata_rp_toWire2.spec
create mode 100644 src/lib/nsas/glue_hints.cc
create mode 100644 src/lib/nsas/glue_hints.h
create mode 100644 src/lib/python/isc/testutils/Makefile.am
create mode 100644 src/lib/python/isc/testutils/README
create mode 100644 src/lib/python/isc/testutils/__init__.py
create mode 100644 src/lib/python/isc/testutils/parse_args.py
delete mode 100644 src/lib/rbmsgq/lib/cc.rb
delete mode 100644 src/lib/rbmsgq/lib/cc/message.rb
delete mode 100644 src/lib/rbmsgq/lib/cc/session.rb
create mode 100644 src/lib/resolve/recursive_query.cc
create mode 100644 src/lib/resolve/recursive_query.h
create mode 100644 src/lib/resolve/tests/recursive_query_unittest.cc
create mode 100644 src/lib/resolve/tests/recursive_query_unittest_2.cc
create mode 100644 src/lib/util/Makefile.am
create mode 100644 src/lib/util/io/Makefile.am
create mode 100644 src/lib/util/io/fd.cc
create mode 100644 src/lib/util/io/fd.h
create mode 100644 src/lib/util/io/fd_share.cc
create mode 100644 src/lib/util/io/fd_share.h
create mode 100644 src/lib/util/io/fdshare_python.cc
create mode 100644 src/lib/util/io/tests/Makefile.am
create mode 100644 src/lib/util/io/tests/fd_share_tests.cc
create mode 100644 src/lib/util/io/tests/fd_tests.cc
create mode 100644 src/lib/util/io/tests/run_unittests.cc
create mode 100644 src/lib/util/unittests/Makefile.am
create mode 100644 src/lib/util/unittests/README
create mode 100644 src/lib/util/unittests/fork.cc
create mode 100644 src/lib/util/unittests/fork.h
delete mode 100644 src/lib/xfr/fd_share.cc
delete mode 100644 src/lib/xfr/fd_share.h
delete mode 100644 src/lib/xfr/fdshare_python.cc
delete mode 100644 src/lib/xfr/python_xfr.cc
-----------------------------------------------------------------------
diff --git a/ChangeLog b/ChangeLog
index 791575e..749b5c3 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,22 +1,166 @@
- 194. [bug] vorner
+218. [func] jinmei
+ src/lib/dns: added support for RP RDATA.
+ (Trac #806, git 4e47d5f6b692c63c907af6681a75024450884a88)
+
+217. [bug] jerry
+ src/lib/dns/python: Use a signed version of larger size of integer and
+ perform more strict range checks with PyArg_ParseTuple() in case of
+ overflows.
+ (Trac #363, git ce281e646be9f0f273229d94ccd75bf7e08d17cf)
+
+216. [func] vorner
+ The BIND10_XFROUT_SOCKET_FILE environment variable can be
+ used to specify which socket should be used for communication
+ between b10-auth and b10-xfrout. Mostly for testing reasons.
+ (Trac #615, git 28b01ad5bf72472c824a7b8fc4a8dc394e22e462)
+
+215. [func] vorner
+ A new process, b10-sockcreator, is added, which will create
+ sockets for the rest of the system. It is the only part
+ which will need to keep the root privileges. However, only
+ the process exists, nothing can talk to it yet.
+ (Trac #366, git b509cbb77d31e388df68dfe52709d6edef93df3f)
+
+214. [func]* vorner
+ Zone manager no longer thinks it is secondary master for
+ all zones in the database. They are listed in
+ Zonemgr/secondary_zones configuration variable (in the form
+ [{"name": "example.com", "class": "IN"}]).
+ (Trac #670, git 7c1e4d5e1e28e556b1d10a8df8d9486971a3f052)
+
+213. [bug] naokikambe
+ Solved incorrect datetime of "bind10.boot_time" and also
+ added a new command "sendstats" for Bob. This command is
+ to send statistics data to the stats daemon immediately.
+ The solved problem is that statistics data doesn't surely
+ reach to the daemon because Bob sent statistics data to
+ the daemon while it is starting. So the daemon invokes the
+ command for Bob after it starts up. This command is also
+ useful for resending statistics data via bindctl manually.
+ (Trac #521, git 1c269cbdc76f5dc2baeb43387c4d7ccc6dc863d2)
+
+212. [bug] naokikambe
+ Fixed that the ModuleCCSession object may group_unsubscribe in the
+ closed CC session in being deleted.
+ (Trac #698, git 0355bddc92f6df66ef50b920edd6ec3b27920d61)
+
+211. [func] shane
+ Implement "--brittle" option, which causes the server to exit
+ if any of BIND 10's processes dies.
+ (Trac #788, git 88c0d241fe05e5ea91b10f046f307177cc2f5bc5)
+
+210. [bug] jerry
+ src/bin/auth: fixed a bug where type ANY queries don't provide
+ additional glue records for ANSWER section.
+ (Trac #699, git 510924ebc57def8085cc0e5413deda990b2abeee)
+
+bind10-devel-20110322 released on March 22, 2011
+
+209. [func] jelte
+ Resolver now uses the NSAS when looking for a nameserver to
+ query for any specific zone. This also includes keeping track of
+ the RTT for that nameserver.
+ (Trac #495, git 76022a7e9f3ff339f0f9f10049aa85e5784d72c5)
+
+208. [bug]* jelte
+ Resolver now answers REFUSED on queries that are not for class IN.
+ This includes the various CH TXT queries, which will be added
+ later.
+ (git 012f9e78dc611c72ea213f9bd6743172e1a2ca20)
+
+207. [func] jelte
+ Resolver now starts listening on localhost:53 if no configuration
+ is set.
+ (Trac #471, git 1960b5becbba05570b9c7adf5129e64338659f07)
+
+206. [func] shane
+ Add the ability to list the running BIND 10 processes using the
+ command channel. To try this, use "Boss show_processes".
+ (Trac #648, git 451bbb67c2b5d544db2f7deca4315165245d2b3b)
+
+205. [bug] jinmei
+ b10-auth, src/lib/datasrc: fixed a bug where b10-auth could return
+ an empty additional section for delegation even if some glue is
+ crucial when it fails to find some other glue records in its data
+ source.
+ (Trac #646, git 6070acd1c5b2f7a61574eda4035b93b40aab3e2b)
+
+204. [bug] jinmei
+ b10-auth, src/lib/datasrc: class ANY queries were not handled
+ correctly in the generic data source (mainly for sqlite3). It
+ could crash b10-auth in the worst case, and could result in
+ incorrect responses in some other cases.
+ (Trac #80, git c65637dd41c8d94399bd3e3cee965b694b633339)
+
+203. [bug] zhang likun
+ Fix resolver cache memory leak: when cache is destructed, rrset
+ and message entries in it are not destructed properly.
+ (Trac #643, git aba4c4067da0dc63c97c6356dc3137651755ffce)
+
+202. [func] vorner
+ It is possible to specify a different directory where we look for
+ configuration files (by -p) and different configuration file to
+ use (-c). Also, it is possible to specify the port on which
+ cmdctl should listen (--cmdctl-port).
+ (Trac #615, git 5514dd78f2d61a222f3069fc94723ca33fb3200b)
+
+201. [bug] jerry
+ src/bin/bindctl: bindctl doesn't show traceback on shutdown.
+ (Trac #588, git 662e99ef050d98e86614c4443326568a0b5be437)
+
+200. [bug] Jelte
+ Fixed a bug where incoming TCP connections were not closed.
+ (Trac #589, git 1d88daaa24e8b1ab27f28be876f40a144241e93b)
+
+199. [func] ocean
+ Cache negative responses (NXDOMAIN/NODATA) from authoritative
+ server for recursive resolver.
+ (Trac #493, git f8fb852bc6aef292555063590c361f01cf29e5ca)
+
+198. [bug] jinmei
+ b10-auth, src/lib/datasrc: fixed a bug where hot spot cache failed
+ to reuse cached SOA for negative responses. Due to this bug
+ b10-auth returned SERVFAIL when it was expected to return a
+ negative response immediately after a specific SOA query for
+ the zone.
+ (Trac #626, git 721a53160c15e8218f6798309befe940b9597ba0)
+
+197. [bug] zhang likun
+ Remove expired message and rrset entries when looking up them
+ in cache, touch or remove the rrset entry in cache properly
+ when doing lookup or update.
+ (Trac #661, git 9efbe64fe3ff22bb5fba46de409ae058f199c8a7)
+
+196. [bug] jinmei
+ b10-auth, src/lib/datasrc: the backend of the in-memory data
+ source could not handle the root name. As a result b10-auth could
+ not work as a root server when using the in-memory data source.
+ (Trac #683, git 420ec42bd913fb83da37b26b75faae49c7957c46)
+
+195. [func] stephen
+ Resolver will now re-try a query over TCP if a response to a UDP
+ query has the TC bit set.
+ (Trac #499, git 4c05048ba059b79efeab53498737abe94d37ee07)
+
+194. [bug] vorner
Solved a 100% CPU usage problem after switching addresses in b10-auth
(and possibly, but unconfirmed, in b10-resolver). It was caused by
repeated reads/accepts on closed socket (the bug was in the code for a
long time, recent changes made it show).
(Trac #657, git e0863720a874d75923ea66adcfbf5b2948efb10a)
- 193. [func]* jreed
+193. [func]* jreed
Listen on the IPv6 (::) and IPv4 (0.0.0.0) wildcard addresses
for b10-auth. This returns to previous behavior prior to
change #184. Document the listen_on configuration in manual.
(Trac #649, git 65a77d8fde64d464c75917a1ab9b6b3f02640ca6)
- 192. [func]* jreed
+192. [func]* jreed
Listen on standard domain port 53 for b10-auth and
b10-resolver.
(Trac #617, #618, git 137a6934a14cf0c5b5c065e910b8b364beb0973f)
- 191. [func] jinmei
+191. [func] jinmei
Imported system test framework of BIND 9. It can be run by
'make systest' at the top source directory. Notes: currently it
doesn't work when built in a separate tree. It also requires
@@ -26,51 +170,51 @@
to the specified file.
(Trac #606, git 6ac000df85625f5921e8895a1aafff5e4be3ba9c)
- 190. [func] jelte
+190. [func] jelte
Resolver now sets random qids on outgoing queries using
the boost::mt19937 prng.
(Trac #583, git 5222b51a047d8f2352bc9f92fd022baf1681ed81)
- 189. [bug] jreed
+189. [bug] jreed
Do not install the log message compiler.
(Trac #634, git eb6441aca464980d00e3ff827cbf4195c5a7afc5)
- 188. [bug] zhang likun
+188. [bug] zhang likun
Make the rrset trust level ranking algorithm used by
isc::cache::MessageEntry::getRRsetTrustLevel() follow RFC2181
section 5.4.1.
(Trac #595 git 19197b5bc9f2955bd6a8ca48a2d04472ed696e81)
- 187. [bug] zhang likun
+187. [bug] zhang likun
Fix the assert error in class isc::cache::RRsetCache by adding the
check for empty pointer and test case for it.
(Trac #638, git 54e61304131965c4a1d88c9151f8697dcbb3ce12)
- 186. [bug] jelte
+186. [bug] jelte
b10-resolver could stop with an assertion failure on certain kinds
of messages (there was a problem in error message creation). This
fixes that.
(Trac #607, git 25a5f4ec755bc09b54410fcdff22691283147f32)
- 185. [bug] vorner
+185. [bug] vorner
Tests use port from private range (53210), lowering chance of
a conflict with something else (eg. running bind 10).
(Trac #523, git 301da7d26d41e64d87c0cf72727f3347aa61fb40)
- 184. [func]* vorner
+184. [func]* vorner
Listening address and port configuration of b10-auth is the same as
for b10-resolver now. That means, it is configured through bindctl
at runtime, in the Auth/listen_on list, not through command line
arguments.
(Trac #575, #576, git f06ce638877acf6f8e1994962bf2dbfbab029edf)
- 183. [bug] jerry
+183. [bug] jerry
src/bin/xfrout: Enable parallel sessions between xfrout server and
muti-Auth. The session needs to be created only on the first time
or if an error occur.
(Trac #419, git 1d60afb59e9606f312caef352ecb2fe488c4e751)
- 182. [func] jinmei
+182. [func] jinmei
Support cppcheck for static code check on C++ code. If cppcheck
is available, 'make cppcheck' on the top source directory will run
the checker and should cleanly complete with an exit code of 0
@@ -80,46 +224,46 @@
the git repository.
(Trac #613, git b973f67520682b63ef38b1451d309be9f4f4b218)
- 181. [func] feng
+181. [func] feng
Add stop interface into dns server, so we can stop each running
server individually. With it, user can reconfigure her running server
with different ip address or port.
(Trac #388, git 6df94e2db856c1adc020f658cc77da5edc967555)
- 180. [build] jreed
+180. [build] jreed
Fix custom DESTDIR for make install. Patch from Jan Engelhardt.
(Trac #629, git 5ac67ede03892a5eacf42ce3ace1e4e376164c9f)
bind10-devel-20110224 released on February 24, 2011
- 179. [func] vorner
+179. [func] vorner
It is possible to start and stop resolver and authoritative
server without restart of the whole system. Change of the
configuration (Boss/start_auth and Boss/start_resolver) is
enough.
(Trac #565, git 0ac0b4602fa30852b0d86cc3c0b4730deb1a58fe)
- 178. [func] jelte
+178. [func] jelte
Resolver now makes (limited) use of the cache
(Trac #491, git 8b41f77f0099ddc7ca7d34d39ad8c39bb1a8363c)
- 177. [func] stephen
+177. [func] stephen
The upstream fetch code in asiolink is now protocol agnostic to
allow for the addition of fallback to TCP if a fetch response
indicates truncation.
(Trac #554, git 9739cbce2eaffc7e80640db58a8513295cf684de)
- 176. [func] zhang likun
+176. [func] likun
src/lib/cache: Rename one interface: from lookupClosestRRset()
to lookupDeepestNS(), and remove one parameter of it.
(Trac #492, git ecbfb7cf929d62a018dd4cdc7a841add3d5a35ae)
- 175. [bug] jerry
+175. [bug] jerry
src/bin/xfrout: Xfrout use the case-sensitive mode to compress
names in an AXFR massage.
(Trac #253, git 004e382616150f8a2362e94d3458b59bb2710182)
- 174. [bug]* jinmei
+174. [bug]* jinmei
src/lib/dns: revised dnssectime functions so that they don't rely
on the time_t type (whose size varies on different systems, which
can lead to subtle bugs like some form of "year 2038 problem").
@@ -128,74 +272,74 @@ bind10-devel-20110224 released on February 24, 2011
should be minimal because these functions are mostly private.
(Trac #61, git 09ece8cdd41c0f025e8b897b4883885d88d4ba5d)
- 173. [bug] jerry
+173. [bug] jerry
python/isc/notify: A notify_out test fails without network
connectivity, encapsulate the socket behavior using a mock
socket class to fix it.
(Trac #346, git 319debfb957641f311102739a15059f8453c54ce)
- 172. [func] jelte
+172. [func] jelte
Improved the bindctl cli in various ways, mainly concerning
list and map item addressing, the correct display of actual values,
and internal help.
(Trac #384, git e5fb3bc1ed5f3c0aec6eb40a16c63f3d0fc6a7b2)
- 171. [func] feng, jerry, jinmei, vorner
+171. [func] vorner
b10-auth, src/lib/datasrc: in memory data source now works as a
complete data source for authoritative DNS servers and b10-auth
uses it. It still misses major features, however, including
DNSSEC support and zone transfer.
- (Last trac #553, but many more,
+ (Last Trac #553, but many more,
git 6f031a09a248e7684723c000f3e8cc981dcdb349)
- 170. [bug] jinmei
+170. [bug] jinmei
Tightened validity checks in the NSEC3 constructors, both "from
"text" and "from wire". Specifically, wire data containing
invalid type bitmaps or invalid lengths of salt or hash is now
correctly rejected.
(Trac #117, git 9c690982f24fef19c747a72f43c4298333a58f48)
- 169. [func] zhang likun, jelte
+169. [func] jelte
Added a basic implementation for a resolver cache (though not
used yet).
(Trac #449, git 8aa3b2246ae095bbe7f855fd11656ae3bdb98986)
- 168. [bug] vorner
+168. [bug] vorner
Boss no longer has the -f argument, which was undocumented and
stayed as a relict of previous versions, currently causing only
strange behaviour.
(Trac #572, git 17f237478961005707d649a661cc72a4a0d612d4)
- 167. [bug] naokikambe
+167. [bug] naokikambe
Fixed failure of termination of msgq_test.py with python3
- coverage(3.3.1)
+ coverage (3.3.1).
(Trac #573, git 0e6a18e12f61cc482e07078776234f32605312e5)
- 166. [func] jelte
+166. [func] jelte
The resolver now sends back a SERVFAIL when there is a client
timeout (timeout_client config setting), but it will not stop
resolving (until there is a lookup timeout or a result).
(Trac #497 and #489, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
- 165. [func] jelte
+165. [func] jelte
The resolver now handles CNAMEs, it will follow them, and include
them in the answer. The maximum length of CNAME chains that is
supported is 16.
(Trac #497, git af0e5cd93bebb27cb5c4457f7759d12c8bf953a6)
- 164. [bug] y-aharen
+164. [bug] y-aharen
IntervalTimer: Modified the interface to accept interval in
milliseconds. It shortens the time of the tests of IntervalTimer.
(Trac #452, git c9f6acc81e24c4b8f0eb351123dc7b43f64e0914)
- 163. [func] vorner
+163. [func] vorner
The pimpl design pattern is used in UDPServer, with a shared
pointer. This makes it smaller to copy (which is done a lot as a
sideeffect of being coroutine) and speeds applications of this
class (notably b10-auth) up by around 10%.
(Trac #537, git 94cb95b1d508541201fc064302ba836164d3cbe6)
- 162. [func] stephen
+162. [func] stephen
Added C++ logging, allowing logging at different severities.
Code specifies the message to be logged via a symbol, and the
logging code picks up the message from an in-built dictionary.
@@ -204,28 +348,28 @@ bind10-devel-20110224 released on February 24, 2011
to create message header files and supply the default messages.
(Trac #438, git 7b1606cea7af15dc71f5ec1d70d958b00aa98af7)
- 161. [func] stephen
+161. [func] stephen
Added ResponseScrubber class to examine response from
a server and to remove out-of-bailiwick RRsets. Also
does cross-section checks to ensure consistency.
(Trac #496, git b9296ca023cc9e76cda48a7eeebb0119166592c5)
- 160. [func] jelte
+160. [func] jelte
Updated the resolver to take 3 different timeout values;
timeout_query for outstanding queries we sent while resolving
timeout_client for sending an answer back to the client
timeout_lookup for stopping the resolving
(currently 2 and 3 have the same final effect)
- (Trac 489, git 578ea7f4ba94dc0d8a3d39231dad2be118e125a2)
+ (Trac #489, git 578ea7f4ba94dc0d8a3d39231dad2be118e125a2)
- 159. [func] smann
+159. [func] smann
The resolver now has a configurable set of root servers to start
resolving at (called root_addresses). By default these are not
(yet) filled in. If empty, a hardcoded address for f-root will be
used right now.
(Trac #483, git a07e078b4feeb01949133fc88c9939254c38aa7c)
- 158. [func] jelte
+158. [func] jelte
The Resolver module will now do (very limited) resolving, if not
set to forwarding mode (i.e. if the configuration option
forward_addresses is left empty). It only supports referrals that
@@ -233,24 +377,24 @@ bind10-devel-20110224 released on February 24, 2011
of authoritative answers.
(Trac #484, git 7b84de4c0e11f4a070e038ca4f093486e55622af)
- 157. [bug] vorner
+157. [bug] vorner
One frozen process no longer freezes the whole b10-msgq. It caused the
whole system to stop working.
(Trac #420, git 93697f58e4d912fa87bc7f9a591c1febc9e0d139)
- 156. [func] stephen
+156. [func] stephen
Added ResponseClassifier class to examine response from
a server and classify it into one of several categories.
(Trac #487, git 18491370576e7438c7893f8551bbb8647001be9c)
bind10-devel-20110120 released on January 20, 2011
- 155. [doc] jreed
+155. [doc] jreed
Miscellaneous documentation improvements for man pages and
the guide, including auth, resolver, stats, xfrout, and
zonemgr. (git c14c4741b754a1eb226d3bdc3a7abbc4c5d727c0)
- 154. [bug] jinmei
+154. [bug] jinmei
b10-xfrin/b10-zonemgr: Fixed a bug where these programs didn't
receive command responses from CC sessions. Eventually the
receive buffer became full, and many other components that rely
@@ -259,12 +403,12 @@ bind10-devel-20110120 released on January 20, 2011
to revisit it for cleaner fix later.
(Trac #516, git 62c72fcdf4617e4841e901408f1e7961255b8194)
- 153. [bug] jelte
+153. [bug] jelte
b10-cfgmgr: Fixed a bug where configuration updates sometimes
lost previous settings in the configuration manager.
(Trac #427, git 2df894155657754151e0860e2ca9cdbed7317c70)
- 152. [func]* jinmei
+152. [func]* jinmei
b10-auth: Added new configuration variable "statistics-interval"
to allow the user to change the timer interval for periodic
statistics updates. The update can also be disabled by setting
@@ -273,46 +417,47 @@ bind10-devel-20110120 released on January 20, 2011
sending statistics and stop responding to queries as a result.
(Trac #513, git 285c5ee3d5582ed6df02d1aa00387f92a74e3695)
- 151. [bug] smann
+151. [bug] smann
lib/log/dummylog.h:
- lib/log/dummylog.cc: Modify dlog so that it takes an optional 2nd
- argument of type bool (true or false). This flag, if set, will cause
- the message to be printed whether or not -v is chosen.
- (trac #432, git 880220478c3e8702d56d761b1e0b21b77d08ee5a)
+ lib/log/dummylog.cc: Modify dlog so that it takes an optional
+ 2nd argument of type bool (true or false). This flag, if
+ set, will cause the message to be printed whether or not
+ -v is chosen.
+ (Trac #432, git 880220478c3e8702d56d761b1e0b21b77d08ee5a)
- 150. [bug] jelte
+150. [bug] jelte
b10-cfgmgr: No longer save the configuration on exit. Configuration
is already saved if it is changed successfully, so writing it on
exit (and hence, when nothing has changed too) is unnecessary and
may even cause problems.
(Trac #435, git fd7baa38c08d54d5b5f84930c1684c436d2776dc)
- 149. [bug] jelte
+149. [bug] jelte
bindctl: Check if the user session has disappeared (either by a
timeout or by a server restart), and reauthenticate if so. This
fixes the 'cmdctl not running' problem.
- (trac #431, git b929be82fec5f92e115d8985552f84b4fdd385b9)
+ (Trac #431, git b929be82fec5f92e115d8985552f84b4fdd385b9)
- 148. [func] jelte
+148. [func] jelte
bindctl: Command results are now pretty-printed (i.e. printed in
a more readable form). Empty results are no longer printed at all
(used to print '{}'), and the message
'send the command to cmd-ctrl' has also been removed.
(git 3954c628c13ec90722a2d8816f52a380e0065bae)
- 147. [bug] jinmei
+147. [bug] jinmei
python/isc/config: Fixed a bug that importing custom configuration
(in b10-config.db) of a remote module didn't work.
(Trac #478, git ea4a481003d80caf2bff8d0187790efd526d72ca)
- 146. [func] jelte
+146. [func] jelte
Command arguments were not validated internally against their
specifications. This change fixes that (on the C++ side, Python
side depends on an as yet planned addition). Note: this is only
an added internal check, the cli already checks format.
(Trac #473, git 5474eba181cb2fdd80e2b2200e072cd0a13a4e52)
- 145. [func]* jinmei
+145. [func]* jinmei
b10-auth: added a new command 'loadzone' for (re)loading a
specific zone. The command syntax is generic but it is currently
only feasible for class IN in memory data source. To reload a
@@ -321,7 +466,7 @@ bind10-devel-20110120 released on January 20, 2011
(Trac #467 git 4f7e1f46da1046de527ab129a88f6aad3dba7562
from 1d7d3918661ba1c6a8b1e40d8fcbc5640a84df12)
- 144. [build] jinmei
+144. [build] jinmei
Introduced a workaround for clang++ build on FreeBSD (and probably
some other OSes). If building BIND 10 fails with clang++ due to
a link error about "__dso_handle", try again from the configure
@@ -332,23 +477,23 @@ bind10-devel-20110120 released on January 20, 2011
variable by hand.
(Trac #474, git cfde436fbd7ddf3f49cbbd153999656e8ca2a298)
- 143. [build] jinmei
+143. [build] jinmei
Fixed build problems with clang++ in unit tests due to recent
changes. No behavior change. (Trac #448, svn r4133)
- 142. [func] jinmei
+142. [func] jinmei
b10-auth: updated query benchmark so that it can test in memory
data source. Also fixed a bug that the output buffer isn't
cleared after query processing, resulting in misleading results
or program crash. This is a regression due to change #135.
(Trac #465, svn r4103)
- 141. [bug] jinmei
+141. [bug] jinmei
b10-auth: Fixed a bug that the authoritative server includes
trailing garbage data in responses. This is a regression due to
change #135. (Trac #462, svn r4081)
- 140. [func] y-aharen
+140. [func] y-aharen
src/bin/auth: Added a feature to count queries and send counter
values to statistics periodically. To support it, added wrapping
class of asio::deadline_timer to use as interval timer.
@@ -359,14 +504,14 @@ bind10-devel-20110120 released on January 20, 2011
counters to b10-stats immediately.
(Trac #347, svn r4026)
- 139. [build] jreed
+139. [build] jreed
Introduced configure option and make targets for generating
Python code coverage report. This adds new make targets:
report-python-coverage and clean-python-coverage. The C++
code coverage targets were renamed to clean-cpp-coverage
and report-cpp-coverage. (Trac #362, svn r4023)
- 138. [func]* jinmei
+138. [func]* jinmei
b10-auth: added a configuration interface to support in memory
data sources. For example, the following command to bindctl
will configure a memory data source containing the "example.com"
@@ -383,70 +528,70 @@ bind10-devel-20110120 released on January 20, 2011
future versions.
(Trac #446, svn r3998)
- 137. [bug] jreed
+137. [bug] jreed
Fix run_*.sh scripts that are used for development testing
so they use a msgq socket file in the build tree.
(Trac #226, svn r3989)
- 136. [bug] jelte
+136. [bug] jelte
bindctl (and the configuration manager in general) now no longer
accepts 'unknown' data; i.e. data for modules that it does not know
about, or configuration items that are not specified in the .spec
files.
(Trac #202, svn r3967)
- 135. [func] each
+135. [func] each
Add b10-resolver. This is an example recursive server that
currently does forwarding only and no caching.
(Trac #327, svn r3903)
- 134. [func] vorner
+134. [func] vorner
b10-resolver supports timeouts and retries in forwarder mode.
(Trac #401, svn r3660)
- 133. [func] vorner
+133. [func] vorner
New temporary logging function available in isc::log. It is used by
b10-resolver.
(Trac #393, r3602)
- 132. [func] vorner
+132. [func] vorner
The b10-resolver is configured through config manager.
It has "listen_on" and "forward_addresses" options.
(Trac #389, r3448)
- 131. [func] feng, jerry
+131. [func] jerry
src/lib/datasrc: Introduced two template classes RBTree and RBNode
to provide the generic map with domain name as key and anything as
the value. Because of some unresolved design issue, the new classes
are only intended to be used by memory zone and zone table.
(Trac #397, svn r3890)
- 130. [func] jerry
+130. [func] jerry
src/lib/datasrc: Introduced a new class MemoryDataSrc to provide
the general interface for memory data source. For the initial
implementation, we don't make it a derived class of AbstractDataSrc
- because the interface is so different(we'll eventually consider this
- as part of the generalization work).
+ because the interface is so different (we'll eventually
+ consider this as part of the generalization work).
(Trac #422, svn r3866)
- 129. [func] jinmei
+129. [func] jinmei
src/lib/dns: Added new functions masterLoad() for loading master
zone files. The initial implementation can only parse a limited
form of master files, but BIND 9's named-compilezone can convert
any valid zone file into the acceptable form.
(Trac #423, svn r3857)
- 128. [build] vorner
+128. [build] vorner
Test for query name = '.', type = DS to authoritative nameserver
for root zone was added.
(Trac #85, svn r3836)
- 127. [bug] stephen
+127. [bug] stephen
During normal operation process termination and resurrection messages
are now output regardless of the state of the verbose flag.
(Trac #229, svn r3828)
- 126. [func] stephen, vorner, ocean
+126. [func] ocean
The Nameserver Address Store (NSAS) component has been added. It takes
care of choosing an IP address of a nameserver when a zone needs to be
contacted.
@@ -454,38 +599,38 @@ bind10-devel-20110120 released on January 20, 2011
bind10-devel-20101201 released on December 01, 2010
- 125. [func] jelte
+125. [func] jelte
Added support for addressing individual list items in bindctl
configuration commands; If you have an element that is a list, you
- can use foo[X] to address a specific item, where X is an integer
+ can use foo[X] integer
(starting at 0)
(Trac #405, svn r3739)
- 124. [bug] jreed
+124. [bug] jreed
Fix some wrong version reporting. Now also show the version
for the component and BIND 10 suite. (Trac #302, svn r3696)
- 123. [bug] jelte
+123. [bug] jelte
src/bin/bindctl printed values had the form of python literals
(e.g. 'True'), while the input requires valid JSON (e.g. 'true').
Output changed to JSON format for consistency. (svn r3694)
- 122. [func] stephen
+122. [func] stephen
src/bin/bind10: Added configuration options to Boss to determine
whether to start the authoritative server, recursive server (or
both). A dummy program has been provided for test purposes.
(Trac #412, svn r3676)
- 121. [func] jinmei
+121. [func] jinmei
src/lib/dns: Added support for TSIG RDATA. At this moment this is
not much of real use, however, because no protocol support was
added yet. It will soon be added. (Trac #372, svn r3649)
- 120. [func] jinmei
+120. [func] jinmei
src/lib/dns: introduced two new classes, TSIGKey and TSIGKeyRing,
to manage TSIG keys. (Trac #381, svn r3622)
- 119. [bug] jinmei
+119. [bug] jinmei
The master file parser of the python datasrc module incorrectly
regarded a domain name beginning with a decimal number as a TTL
specification. This confused b10-loadzone and had it reject to
@@ -495,158 +640,158 @@ bind10-devel-20101201 released on December 01, 2010
from a TTL specification. This is part of a more general issue
and will be addressed in Trac #413. (Trac #411, svn r3599)
- 118. [func] jinmei
+118. [func] jinmei
src/lib/dns: changed the interface of
AbstractRRset::getRdataIterator() so that the internal
cursor would point to the first RDATA automatically. This
will be a more intuitive and less error prone behavior.
This is a backward compatible change. (Trac #410, r3595)
- 117. [func] jinmei
+117. [func] jinmei
src/lib/datasrc: added new zone and zone table classes for the
support of in memory data source. This is an intermediate step to
the bigger feature, and is not yet actually usable in practice.
(Trac #399, svn r3590)
- 116. [bug] jerry
+116. [bug] jerry
src/bin/xfrout: Xfrout and Auth will communicate by long tcp
connection, Auth needs to make a new connection only on the first
time or if an error occurred.
(Trac #299, svn r3482)
- 115. [func]* jinmei
+115. [func]* jinmei
src/lib/dns: Changed DNS message flags and section names from
separate classes to simpler enums, considering the balance between
type safety and usability. API has been changed accordingly.
More documentation and tests were provided with these changes.
(Trac #358, r3439)
- 114. [build] jinmei
+114. [build] jinmei
Supported clang++. Note: Boost >= 1.44 is required.
(Trac #365, svn r3383)
- 113. [func]* zhanglikun
+113. [func]* zhanglikun
Folder name 'utils'(the folder in /src/lib/python/isc/) has been
renamed to 'util'. Programs that used 'import isc.utils.process'
now need to use 'import isc.util.process'. The folder
/src/lib/python/isc/Util is removed since it isn't used by any
program. (Trac #364, r3382)
- 112. [func] zhang likun
+112. [func] zhang likun
Add one mixin class to override the naive serve_forever() provided
in python library socketserver. Instead of polling for shutdown
every poll_interval seconds, one socketpair is used to wake up
the waiting server. (Trac #352, svn r3366)
- 111. [bug]* zhanglikun, Michal Vaner
+111. [bug]* Vaner
Make sure process xfrin/xfrout/zonemgr/cmdctl can be stopped
properly when user enter "ctrl+c" or 'Boss shutdown' command
through bindctl. The ZonemgrRefresh.run_timer and
NotifyOut.dispatcher spawn a thread themselves.
(Trac #335, svn r3273)
- 110. [func] Michal Vaner
+110. [func] Vaner
Added isc.net.check module to check ip addresses and ports for
correctness and isc.net.addr to hold IP address. The bind10, xfrin
and cmdctl programs are modified to use it.
(Trac #353, svn r3240)
- 109. [func] naokikambe
+109. [func] naokikambe
Added the initial version of the stats module for the statistics
feature of BIND 10, which supports the restricted features and
items and reports via bindctl command. (Trac #191, r3218)
Added the document of the stats module, which is about how stats
module collects the data (Trac #170, [wiki:StatsModule])
- 108. [func] jerry
+108. [func] jerry
src/bin/zonemgr: Provide customizable configurations for
lowerbound_refresh, lowerbound_retry, max_transfer_timeout and
jitter_scope. (Trac #340, r3205)
- 107. [func] zhang likun
+107. [func] likun
Remove the parameter 'db_file' for command 'retransfer' of
xfrin module. xfrin.spec will not be generated by script.
(Trac #329, r3171)
- 106. [bug] zhang likun
+106. [bug] likun
When xfrin can't connect with one zone's master, it should tell
the bad news to zonemgr, so that zonemgr can reset the timer for
that zone. (Trac #329, r3170)
- 105. [bug] Michal Vaner
+105. [bug] Vaner
Python processes: they no longer take 100% CPU while idle
due to a busy loop in reading command session in a nonblocking way.
(Trac #349, svn r3153), (Trac #382, svn r3294)
- 104. [bug] jerry
+104. [bug] jerry
bin/zonemgr: zonemgr should be attempting to refresh expired zones.
(Trac #336, r3139)
- 103. [bug] jerry
+103. [bug] jerry
lib/python/isc/log: Fixed an issue with python logging,
python log shouldn't die with OSError. (Trac #267, r3137)
- 102. [build] jinmei
+102. [build] jinmei
Disable threads in ASIO to minimize build time dependency.
(Trac #345, r3100)
- 101. [func] jinmei
+101. [func] jinmei
src/lib/dns: Completed Opcode and Rcode implementation with more
tests and documentation. API is mostly the same but the
validation was a bit tightened. (Trac #351, svn r3056)
- 100. [func] Michal Vaner
+100. [func] Vaner
Python processes: support naming of python processes so
they're not all called python3.
(Trac #322, svn r3052)
- 99. [func]* jinmei
+99. [func]* jinmei
Introduced a separate EDNS class to encapsulate EDNS related
information more cleanly. The related APIs are changed a bit,
although it won't affect most of higher level applications.
(Trac #311, svn r3020)
- 98. [build] jinmei
+98. [build] jinmei
The ./configure script now tries to search some common include
paths for boost header files to minimize the need for explicit
configuration with --with-boost-include. (Trac #323, svn r3006)
- 97. [func] jinmei
+97. [func] jinmei
Added a micro benchmark test for query processing of b10-auth.
(Trac #308, svn r2982)
- 96. [bug] jinmei
+96. [bug] jinmei
Fixed two small issues with configure: Do not set CXXFLAGS so that
it can be customized; Make sure --disable-static works.
(Trac #325, r2976)
bind10-devel-20100917 released on September 17, 2010
- 95. [doc] jreed
+95. [doc] jreed
Add b10-zonemgr manual page. Update other docs to introduce
this secondary manager. (Trac #341, svn r2951)
- 95. [bug] jreed
+95. [bug] jreed
bin/xfrout and bin/zonemgr: Fixed some stderr output.
(Trac #342, svn r2949)
- 94. [bug] jelte
+94. [bug] jelte
bin/xfrout: Fixed a problem in xfrout where only 2 or 3 RRs
were used per DNS message in the xfrout stream.
(Trac #334, r2931)
- 93. [bug] jinmei
+93. [bug] jinmei
lib/datasrc: A DS query could crash the library (and therefore,
e.g. the authoritative server) if some RR of the same apex name
is stored in the hot spot cache. (Trac #307, svn r2923)
- 92. [func]* jelte
+92. [func]* jelte
libdns_python (the python wrappers for libdns++) has been renamed
to pydnspp (Python DNS++). Programs and libraries that used
'import libdns_python' now need to use 'import pydnspp'.
(Trac #314, r2902)
- 91. [func]* jinmei
+91. [func]* jinmei
lib/cc: Use const pointers and const member functions for the API
as much as possible for safer operations. Basically this does not
change the observable behavior, but some of the API were changed
@@ -654,36 +799,36 @@ bind10-devel-20100917 released on September 17, 2010
copies, but at this moment the overhead is deemed acceptable.
(Trac #310, r2803)
- 90. [build] jinmei
+90. [build] jinmei
(Darwin/Mac OS X specific) Specify DYLD_LIBRARY_PATH for tests and
experimental run under the source tree. Without this loadable
python modules refer to installation paths, which may confuse the
operation due to version mismatch or even trigger run time errors
due to missing libraries. (Trac #313, r2782)
- 89. [build] jinmei
+89. [build] jinmei
Generate b10-config.db for tests at build time so that the source
tree does not have to be writable. (Trac #315, r2776)
- 88. [func] jelte
+88. [func] jelte
Blocking reads on the msgq command channel now have a timeout
(defaults to 4 seconds, modifiable as needed by modules).
Because of this, modules will no longer block indefinitely
if they are waiting for a message that is not sent for whatever
reason. (Trac #296, r2761)
- 87. [func] zhanglikun
+87. [func] zhanglikun
lib/python/isc/notifyout: Add the feature of notify-out, when
zone axfr/ixfr finishing, the server will notify its slaves.
(Trac #289, svn r2737)
- 86. [func] jerry
+86. [func] jerry
bin/zonemgr: Added zone manager module. The zone manager is one
of the co-operating processes of BIND10, which keeps track of
timers and other information necessary for BIND10 to act as a
slave. (Trac #215, svn r2737)
- 85. [build]* jinmei
+85. [build]* jinmei
Build programs using dynamic link by default. A new configure
option --enable-static-link is provided to force static link for
executable programs. Statically linked programs can be run on a
@@ -692,34 +837,34 @@ bind10-devel-20100917 released on September 17, 2010
bind10-devel-20100812 released on August 12, 2010
- 84. [bug] jinmei, jerry
+84. [bug] jinmei, jerry
This is a quick fix patch for the issue: AXFR fails half the
time because of connection problems. xfrout client will make
a new connection every time. (Trac #299, svn r2697)
- 83. [build]* jreed
+83. [build]* jreed
The configure --with-boost-lib option is removed. It was not
used since the build included ASIO. (svn r2684)
- 82. [func] jinmei
+82. [func] jinmei
bin/auth: Added -u option to change the effective process user
of the authoritative server after invocation. The same option to
the boss process will be propagated to b10-auth, too.
(Trac #268, svn r2675)
- 81. [func] jinmei
+81. [func] jinmei
Added a C++ framework for micro benchmark tests. A supplemental
library functions to build query data for the tests were also
provided. (Trac #241, svn r2664)
- 80. [bug] jelte
+80. [bug] jelte
bindctl no longer accepts configuration changes for unknown or
non-running modules (for the latter, this is until we have a
way to verify those options, at which point it'll be allowed
again).
(Trac #99, r2657)
- 79. [func] feng, jinmei
+79. [func] feng, jinmei
Refactored the ASIO link interfaces to move incoming XFR and
NOTIFY processing to the auth server class. Wrapper classes for
ASIO specific concepts were also provided, so that other BIND 10
@@ -731,7 +876,7 @@ bind10-devel-20100812 released on August 12, 2010
Note: Right now, NOTIFY doesn't actually trigger subsequent zone
transfer due to security reasons. (Trac #221, r2565)
- 78. [bug] jinmei
+78. [bug] jinmei
lib/dns: Fixed miscellaneous bugs in the base32 (hex) and hex
(base16) implementation, including incorrect padding handling,
parser failure in decoding with a SunStudio build, missing
@@ -742,44 +887,44 @@ bind10-devel-20100812 released on August 12, 2010
libdns++, so we don't consider it a backward incompatible change.
(Trac #256, r2549)
- 77. [func] zhanglikun
+77. [func] zhanglikun
Make error message be more friendly when running cmdctl and it's
- already running(listening on same port)(Trac #277, r2540)
+ already running (listening on same port)(Trac #277, r2540)
- 76. [bug] jelte
+76. [bug] jelte
Fixed a bug in the handling of 'remote' config modules (i.e.
modules that peek at the configuration of other modules), where
they answered 'unknown command' to commands for those other
modules. (Trac #278, r2506)
- 75. [bug] jinmei
+75. [bug] jinmei
Fixed a bug in the sqlite3 data source where temporary strings
could be referenced after destruction. It caused various lookup
failures with SunStudio build. (Trac #288, r2494)
- 74. [func]* jinmei
+74. [func]* jinmei
Refactored the cc::Session class by introducing an abstract base
class. Test code can use their own derived mock class so that
tests can be done without establishing a real CC session. This
change also modified some public APIs, mainly in the config
module. (Trac #275, r2459)
- 73. [bug] jelte
+73. [bug] jelte
Fixed a bug where in bindctl, locally changed settings were
reset when the list of running modules is updated. (Trac #285,
r2452)
- 72. [build] jinmei
+72. [build] jinmei
Added -R when linking python wrapper modules to libpython when
possible. This helps build BIND 10 on platforms that install
libpython whose path is unknown to run-time loader. NetBSD is a
known such platform. (Trac #148, r2427)
- 71. [func] each
+71. [func] each
Add "-a" (address) option to bind10 to specify an address for
the auth server to listen on.
- 70. [func] each
+70. [func] each
Added a hot-spot cache to libdatasrc to speed up access to
repeatedly-queried data and reduce the number of queries to
the underlying database; this should substantially improve
@@ -789,22 +934,22 @@ bind10-devel-20100812 released on August 12, 2010
bind10-devel-20100701 released on July 1, 2010
- 69. [func]* jelte
+69. [func]* jelte
Added python wrappers for libdns++ (isc::dns), and libxfr. This
removes the dependency on Boost.Python. The wrappers don't
completely implement all functionality, but the high-level API
is wrapped, and current modules use it now.
(Trac #181, svn r2361)
- 68. [func] zhanglikun
- Add options -c(--certificate-chain) to bindctl. Override class
+68. [func] zhanglikun
+ Add options -c (--certificate-chain) to bindctl. Override class
HTTPSConnection to support server certificate validation.
Add support to cmdctl.spec file, now there are three configurable
items for cmdctl: 'key_file', 'cert_file' and 'accounts_file',
all of them can be changed in runtime.
(Trac #127, svn r2357)
- 67. [func] zhanglikun
+67. [func] zhanglikun
Make bindctl's command parser only do minimal check.
Parameter value can be a sequence of non-space characters,
or a string surrounded by quotation marks (these marks can
@@ -815,13 +960,13 @@ bind10-devel-20100701 released on July 1, 2010
avoid using Exception to catch all exceptions.
(Trac #220, svn r2356)
- 66. [bug] each
+66. [bug] each
Check for duplicate RRsets before inserting data into a message
section; this, among other things, will prevent multiple copies
of the same CNAME from showing up when there's a loop. (Trac #69,
svn r2350)
- 65. [func] shentingting
+65. [func] shentingting
Various loadzone improvements: allow optional comment for
$TTL, allow optional origin and comment for $INCLUDE, allow
optional comment for $ORIGIN, support BIND9 extension of
@@ -832,71 +977,71 @@ bind10-devel-20100701 released on July 1, 2010
formats to load.
(Trac #197, #199, #244, #161, #198, #174, #175, svn r2340)
- 64. [func] jerry
+64. [func] jerry
Added python logging framework. It is for testing and
experimenting with logging ideas. Currently, it supports
three channels (file, syslog and stderr) and five levels
(debug, info, warning, error and critical).
(Trac #176, svn r2338)
- 63. [func] shane
+63. [func] shane
Added initial support for setuid(), using the "-u" flag. This will
be replaced in the future, but for now provides a reasonable
starting point.
(Trac #180, svn r2330)
- 62. [func] jelte
+62. [func] jelte
bin/xfrin: Use the database_file as configured in Auth to transfers
bin/xfrout: Use the database_file as configured in Auth to transfers
- 61. [bug] jelte
+61. [bug] jelte
bin/auth: Enable b10-auth to be launched in source tree
(i.e. use a zone database file relative to that)
- 60. [build] jinmei
+60. [build] jinmei
Supported SunStudio C++ compiler. Note: gtest still doesn't work.
(Trac #251, svn r2310)
- 59. [bug] jinmei
+59. [bug] jinmei
lib/datasrc,bin/auth: The authoritative server could return a
SERVFAIL with a partial answer if it finds a data source broken
while looking for an answer. This can happen, for example, if a
zone that doesn't have an NS RR is configured and loaded as a
sqlite3 data source. (Trac #249, r2286)
- 58. [bug] jinmei
+58. [bug] jinmei
Worked around an interaction issue between ASIO and standard C++
library headers. Without this ASIO didn't work: sometimes the
application crashes, sometimes it blocked in the ASIO module.
(Trac #248, svn r2187, r2190)
- 57. [func] jinmei
+57. [func] jinmei
lib/datasrc: used a simpler version of Name::split (change 31) for
better readability. No behavior change. (Trac #200, svn r2159)
- 56. [func]* jinmei
+56. [func]* jinmei
lib/dns: renamed the library name to libdns++ to avoid confusion
with the same name of library of BIND 9.
(Trac #190, svn r2153)
- 55. [bug] shane
+55. [bug] shane
bin/xfrout: xfrout exception on Ctrl-C now no longer generates
exception for 'Interrupted system call'
(Track #136, svn r2147)
- 54. [bug] zhanglikun
+54. [bug] zhanglikun
bin/xfrout: Enable b10-xfrout can be launched in source
code tree.
(Trac #224, svn r2103)
- 53. [bug] zhanglikun
+53. [bug] zhanglikun
bin/bindctl: Generate a unique session ID by using
socket.gethostname() instead of socket.gethostbyname(),
since the latter one could make bindctl stall if its own
host name can't be resolved.
(Trac #228, svn r2096)
- 52. [func] zhanglikun
+52. [func] zhanglikun
bin/xfrout: When xfrout is launched, check whether the
socket file is being used by one running xfrout process,
if it is, exit from python. If the file isn't a socket file
@@ -906,210 +1051,210 @@ bind10-devel-20100701 released on July 1, 2010
bind10-devel-20100602 released on June 2, 2010
- 51. [build] jelte
+51. [build] jelte
lib/python: Add bind10_config.py module for paths and
possibly other configure-time variables. Allow some components
to find spec files in build tree when ran from source.
(Trac #223)
- 50. [bug] zhanglikun
+50. [bug] zhanglikun
bin/xfrin: a regression in xfrin: it can't communicate with
a remote server. (Trac #218, svn r2038)
- 49. [func]* jelte
+49. [func]* jelte
Use unix domain sockets for msgq. For b10-msgq, the command
line options --msgq-port and -m were removed. For bind10,
the -msgq-port option was removed, and the -m command line
option was changed to be a filename (instead of port number).
(Trac #183, svn r2009)
- 48. [func] jelte
+48. [func] jelte
bin/auth: Use asio's io_service for the msgq handling.
(svn r2007)
- 47. [func] zhanglikun
+47. [func] zhanglikun
bin/cmdctl: Add value/type check for commands sent to
cmdctl. (Trac #201, svn r1959)
- 46. [func] zhanglikun
+46. [func] zhanglikun
lib/cc: Fix real type data encoding/decoding. (Trac #193,
svn r1959)
- 45. [func] zhanglikun
+45. [func] zhanglikun
bin/bind10: Pass verbose option to more modules. (Trac
#205, svn r1957)
- 44. [build] jreed
+44. [build] jreed
Install headers for libdns and libexception. (Trac #68,
svn r1941)
- 43. [func] jelte
+43. [func] jelte
lib/cc: Message queuing on cc channel. (Trac #58, svn r1870)
- 42. [func] jelte
+42. [func] jelte
lib/python/isc/config: Make temporary file with python
tempfile module instead of manual with fixed name. (Trac
#184, svn r1859)
- 41. [func] jelte
+41. [func] jelte
Module descriptions in spec files. (Trac #90, svn r1856)
- 40. [build] jreed
+40. [build] jreed
Report detected features and configure settings at end of
configure output. (svn r1836)
- 39. [func]* each
+39. [func]* each
Renamed libauth to libdatasrc.
- 38. [bug] zhanglikun
+38. [bug] zhanglikun
Send command 'shutdown' to Xfrin and Xfrout when boss receive SIGINT.
Remove unused socket file when Xfrout process exits. Make sure Xfrout
exit by itself when it receives SIGINT, instead of being killed by the
signal SIGTERM or SIGKILL sent from boss.
(Trac #135, #151, #134, svn r1797)
- 37. [build] jinmei
+37. [build] jinmei
Check for the availability of python-config. (Trac #159,
svn r1794)
- 36. [func] shane
+36. [func] shane
bin/bind10: Miscellaneous code cleanups and improvements.
(Trac #40, svn r2012)
- 35. [bug] jinmei
+35. [bug] jinmei
bin/bindctl: fixed a bug that it didn't accept IPv6 addresses as
command arguments. (Trac #219, svn r2022)
- 34. [bug] jinmei
+34. [bug] jinmei
bin/xfrin: fixed several small bugs with many additional unit
tests. Fixes include: IPv6 transport support, resource leak,
and non IN class support. (Trac #185, svn r2000)
- 33. [bug] each
+33. [bug] each
bin/auth: output now prepended with "[b10-auth]" (Trac
#109, svn r1985)
- 32. [func]* each
+32. [func]* each
bin/auth: removed custom query-processing code, changed
boost::asio code to use plain asio instead, and added asio
headers to the source tree. This allows building without
using an external boost library. (Trac #163, svn r1983)
- 31. [func] jinmei
+31. [func] jinmei
lib/dns: added a separate signature for Name::split() as a
convenient wrapper for common usage. (Trac #49, svn r1903)
- 30. [bug] jinmei
+30. [bug] jinmei
lib/dns: parameter validation of Name::split() was not sufficient,
and invalid parameters could cause integer overflow and make the
library crash. (Trac #177, svn r1806)
bind10-devel-20100421 released on April 21, 2010
- 29. [build]
+29. [build] jreed
Enable Python unit tests for "make check". (svn r1762)
- 28. [bug]
+28. [bug] jreed
Fix msgq CC test so it can find its module. (svn r1751)
- 27. [build]
+27. [build] jelte
Add missing copyright license statements to various source
files. (svn r1750)
- 26. [func]
+26. [func] jelte
Use PACKAGE_STRING (name + version) from config.h instead
of hard-coded value in CH TXT version.bind replies (Trac
#114, svn r1749)
- 25. [func]*
+25. [func]* jreed
Renamed msgq to b10-msgq. (Trac #25, svn r1747, r1748)
- 24. [func]
+24. [func] jinmei
Support case-sensitive name compression in MessageRenderer.
(Trac #142, svn r1704)
- 23. [func]
+23. [func] jinmei
Support a simple name with possible compression. (svn r1701)
- 22. [func]
+22. [func] zhanglikun
b10-xfrout for AXFR-out support added. (svn r1629, r1630)
- 21. [bug]
+21. [bug] zhanglikun
Make log message more readable when xfrin failed. (svn
r1697)
- 20. [bug]
+20. [bug] jinmei
Keep stderr for child processes if -v is specified. (svn
r1690, r1698)
- 19. [bug]
+19. [bug] jinmei
Allow bind10 boss to pass environment variables from parent.
(svn r1689)
- 18. [bug]
+18. [bug] jinmei
Xfrin warn if bind10_dns load failed. (svn r1688)
- 17. [bug]
+17. [bug] jinmei
Use sqlite3_ds.load() in xfrin module and catch Sqlite3DSError
explicitly. (svn r1684)
- 16. [func]*
+16. [func]* zhanglikun
Removed print_message and print_settings configuration
commands from Xfrin. (Trac #136, svn r1682)
- 15. [func]*
+15. [func]* jinmei
Changed zone loader/updater so trailing dot is not required.
(svn r1681)
- 14. [bug]
+14. [bug] shane
Change shutdown to actually SIGKILL properly. (svn r1675)
- 13. [bug]
+13. [bug] jinmei
Don't ignore other RRs than SOA even if the second SOA is
found. (svn r1674)
- 12. [build]
+12. [build] jreed
Fix tests and testdata so can be used from a read-only
source directory.
- 11. [build]
+11. [build] jreed
Make sure python tests scripts are included in tarball.
(svn r1648)
- 10. [build]
+10. [build] jinmei
Improve python detection for configure. (svn r1622)
- 9. [build]
+9. [build] jinmei
Automake the python binding of libdns. (svn r1617)
- 8. [bug]
+8. [bug] zhanglikun
Fix log errors which may cause xfrin module to crash. (svn
r1613)
- 7. [func]
+7. [func] zhanglikun
New API for inserting zone data to sqlite3 database for
AXFR-in. (svn r1612, r1613)
- 6. [bug]
+6. [bug] jreed
More code review, miscellaneous cleanups, style guidelines,
and new and improved unit tests added.
- 5. [doc]
+5. [doc] jreed
Manual page cleanups and improvements.
- 4. [bug]
+4. [bug] jinmei
NSEC RDATA fixes for buffer overrun lookups, incorrect
boundary checks, spec-non-conformant behaviors. (svn r1611)
- 3. [bug]
+3. [bug] jelte
Remove a re-raise of an exception that should only have
been included in an error answer on the cc channel. (svn
r1601)
- 2. [bug]
+2. [bug] mgraff
Removed unnecessary sleep() from ccsession.cc. (svn r1528)
- 1. [build]*
+1. [build]* jreed
The configure --with-boostlib option changed to --with-boost-lib.
bind10-devel-20100319 released on March 19, 2010
@@ -1128,10 +1273,10 @@ LEGEND
unless it's deemed to be impossible or very hard to keep
compatibility to fix the bug.
[build] compilation and installation infrastructure change.
-[doc] update to documentation. This shouldn't change run time behavior.
+[doc] update to documentation. This shouldn't change run time behavior.
[func] new feature. In some cases this may be a backward incompatible
change, which would require a bump of major version.
-[security] security hole fix. This is no different than a general bug fix
- except that it will be handled as confidential and will cause
+[security] security hole fix. This is no different than a general bug
+ fix except that it will be handled as confidential and will cause
security patch releases.
*: Backward incompatible or operational change.
diff --git a/INSTALL b/INSTALL
index 6ab63ea..44c380a 100644
--- a/INSTALL
+++ b/INSTALL
@@ -1,5 +1,5 @@
-To build "configure" file:
- autoreconf
+If using git (not the tarball), build the "configure" file:
+ autoreconf --install
To then build from source:
./configure
diff --git a/README b/README
index b10d12e..5320a6e 100644
--- a/README
+++ b/README
@@ -15,9 +15,9 @@ five year plan are described here:
This release includes the bind10 master process, b10-msgq message
bus, b10-auth authoritative DNS server (with SQLite3 and in-memory
-backends), b10-resolver forwarding DNS server, b10-cmdctl remote
-control daemon, b10-cfgmgr configuration manager, b10-xfrin AXFR
-inbound service, b10-xfrout outgoing AXFR service, b10-zonemgr
+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, and a new libdns++ library for C++ with a python wrapper.
diff --git a/configure.ac b/configure.ac
index acc7628..8e16d3b 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.59])
-AC_INIT(bind10-devel, 20110224, bind10-dev at isc.org)
+AC_INIT(bind10-devel, 20110322, bind10-dev at isc.org)
AC_CONFIG_SRCDIR(README)
AM_INIT_AUTOMAKE
AC_CONFIG_HEADERS([config.h])
@@ -624,6 +624,8 @@ AC_CONFIG_FILES([Makefile
src/bin/auth/benchmarks/Makefile
src/bin/resolver/Makefile
src/bin/resolver/tests/Makefile
+ src/bin/sockcreator/Makefile
+ src/bin/sockcreator/tests/Makefile
src/bin/xfrin/Makefile
src/bin/xfrin/tests/Makefile
src/bin/xfrout/Makefile
@@ -663,6 +665,7 @@ AC_CONFIG_FILES([Makefile
src/lib/python/isc/net/tests/Makefile
src/lib/python/isc/notify/Makefile
src/lib/python/isc/notify/tests/Makefile
+ src/lib/python/isc/testutils/Makefile
src/lib/config/Makefile
src/lib/config/tests/Makefile
src/lib/config/tests/testdata/Makefile
@@ -691,6 +694,10 @@ AC_CONFIG_FILES([Makefile
src/lib/server_common/tests/Makefile
tests/Makefile
tests/system/Makefile
+ src/lib/util/Makefile
+ src/lib/util/io/Makefile
+ src/lib/util/io/tests/Makefile
+ src/lib/util/unittests/Makefile
])
AC_OUTPUT([doc/version.ent
src/bin/cfgmgr/b10-cfgmgr.py
@@ -705,6 +712,7 @@ AC_OUTPUT([doc/version.ent
src/bin/xfrout/xfrout.py
src/bin/xfrout/xfrout.spec.pre
src/bin/xfrout/tests/xfrout_test
+ src/bin/xfrout/tests/xfrout_test.py
src/bin/xfrout/run_b10-xfrout.sh
src/bin/resolver/resolver.spec.pre
src/bin/resolver/spec_config.h.pre
@@ -719,9 +727,8 @@ AC_OUTPUT([doc/version.ent
src/bin/stats/run_b10-stats_stub.sh
src/bin/stats/tests/stats_test
src/bin/bind10/bind10.py
- src/bin/bind10/tests/bind10_test
- src/bin/bind10/tests/bind10_test.py
src/bin/bind10/run_bind10.sh
+ src/bin/bind10/tests/bind10_test.py
src/bin/bindctl/run_bindctl.sh
src/bin/bindctl/bindctl_main.py
src/bin/bindctl/tests/bindctl_test
diff --git a/doc/Doxyfile b/doc/Doxyfile
index 46aa178..83e85b8 100644
--- a/doc/Doxyfile
+++ b/doc/Doxyfile
@@ -568,7 +568,11 @@ WARN_LOGFILE =
# directories like "/usr/src/myproject". Separate the files or directories
# with spaces.
-INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ ../src/lib/nsas ../src/lib/testutils ../src/lib/cache ../src/lib/server_common/
+INPUT = ../src/lib/cc ../src/lib/config ../src/lib/dns \
+ ../src/lib/exceptions ../src/lib/datasrc ../src/bin/auth \
+ ../src/bin/resolver ../src/lib/bench ../src/lib/log ../src/lib/asiolink/ \
+ ../src/lib/nsas ../src/lib/testutils ../src/lib/cache \
+ ../src/lib/server_common/ ../src/bin/sockcreator/ ../src/lib/util/
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is
diff --git a/doc/guide/bind10-guide.html b/doc/guide/bind10-guide.html
index fe6bd93..a631a9c 100644
--- a/doc/guide/bind10-guide.html
+++ b/doc/guide/bind10-guide.html
@@ -1,19 +1,19 @@
-<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110224. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
- 20110224.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
+<html><head><meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"><title>BIND 10 Guide</title><link rel="stylesheet" href="./bind10-guide.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.75.2"><meta name="description" content="BIND 10 is a Domain Name System (DNS) suite managed by Internet Systems Consortium (ISC). It includes DNS libraries and modular components for controlling authoritative and recursive DNS servers. This is the reference guide for BIND 10 version 20110322. The most up-to-date version of this document, along with other documents for BIND 10, can be found at ."></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="book" title="BIND 10 Guide"><div class="titlepage"><div><div><h1 class="title"><a name="id1168230298903"></a>BIND 10 Guide</h1></div><div><h2 class="subtitle">Administrator Reference for BIND 10</h2></div><div><p class="releaseinfo">This is the referenc
e guide for BIND 10 version
+ 20110322.</p></div><div><p class="copyright">Copyright © 2010 Internet Systems Consortium, Inc.</p></div><div><div class="abstract" title="Abstract"><p class="title"><b>Abstract</b></p><p>BIND 10 is a Domain Name System (DNS) suite managed by
Internet Systems Consortium (ISC). It includes DNS libraries
and modular components for controlling authoritative and
recursive DNS servers.
</p><p>
- This is the reference guide for BIND 10 version 20110224.
+ This is the reference guide for BIND 10 version 20110322.
The most up-to-date version of this document, along with
- other documents for BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>. </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285028">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285047">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285108">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285205">Build</a></span></dt><dt><span class="section"><a href="#id1168230285221">Install</a></span></dt><dt><span class="section"><a href="#id1168230285244">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285822">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285888">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285918">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
+ other documents for BIND 10, can be found at <a class="ulink" href="http://bind10.isc.org/docs" target="_top">http://bind10.isc.org/docs</a>. </p></div></div></div><hr></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="chapter"><a href="#intro">1. Introduction</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#installation">2. Installation</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">In
stallation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285021">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285041">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285101">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285198">Build</a></span></dt><dt><span class="section"><a href="#id1168230285214">Install</a></span></dt><dt><span class="section"><a href="#id1168230285238">Install Hierarchy</a></span></dt></dl></dd></dl></dd><dt><span class="chapter"><a href="#bind10">3. Starting BIND10 with <span class="command"><strong>bind10</strong></span></a></span></dt><dd><dl><dt><span class="section"><a href="#start">Starting BIND 10</a></span></dt></dl></dd><dt><span class="chapter"><a href="#msgq">4. Command channel</a></span></dt><dt><span class="chapter"><a href="#cfgmgr">5. Configuration manager</a></span></dt><dt><span class="chapter"><a hr
ef="#cmdctl">6. Remote control daemon</a></span></dt><dd><dl><dt><span class="section"><a href="#cmdctl.spec">Configuration specification for b10-cmdctl</a></span></dt></dl></dd><dt><span class="chapter"><a href="#bindctl">7. Control and configure user interface</a></span></dt><dt><span class="chapter"><a href="#authserver">8. Authoritative Server</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285812">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285877">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285908">Loading Master Zones Files</a></span></dt></dl></dd><dt><span class="chapter"><a href="#xfrin">9. Incoming Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#xfrout">10. Outbound Zone Transfers</a></span></dt><dt><span class="chapter"><a href="#zonemgr">11. Secondary Manager</a></span></dt><dt><span class="chapter"><a href="#resolverserver">12. Recursive Name Server<
/a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230286296">Forwarding</a></span></dt></dl></dd><dt><span class="chapter"><a href="#statistics">13. Statistics</a></span></dt></dl></div><div class="chapter" title="Chapter 1. Introduction"><div class="titlepage"><div><div><h2 class="title"><a name="intro"></a>Chapter 1. Introduction</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230299038">Supported Platforms</a></span></dt><dt><span class="section"><a href="#id1168230299065">Required Software</a></span></dt><dt><span class="section"><a href="#starting_stopping">Starting and Stopping the Server</a></span></dt><dt><span class="section"><a href="#managing_once_running">Managing BIND 10</a></span></dt></dl></div><p>
BIND is the popular implementation of a DNS server, developer
interfaces, and DNS tools.
BIND 10 is a rewrite of BIND 9. BIND 10 is written in C++ and Python
and provides a modular environment for serving and maintaining DNS.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
This guide covers the experimental prototype of
- BIND 10 version 20110224.
+ BIND 10 version 20110322.
</p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
BIND 10 provides a EDNS0- and DNSSEC-capable
authoritative DNS server and a caching recursive name server
@@ -132,7 +132,7 @@
and, of course, DNS. These include detailed developer
documentation and code examples.
- </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285028">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285047">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285108">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285205">Build</a></span></dt><dt><span class="section"><a href="#id1168230285221">Install</a></span></dt><dt><span class="section"><a href="#id1168230285244">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284842"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="chapter" title="Chapter 2. Installation"><div class="titlepage"><div><div><h2 class="title"><a name="installation"></a>Chapter 2. Installation</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230284842">Building Requirements</a></span></dt><dt><span class="section"><a href="#quickstart">Quick start</a></span></dt><dt><span class="section"><a href="#install">Installation from source</a></span></dt><dd><dl><dt><span class="section"><a href="#id1168230285021">Download Tar File</a></span></dt><dt><span class="section"><a href="#id1168230285041">Retrieve from Git</a></span></dt><dt><span class="section"><a href="#id1168230285101">Configure before the build</a></span></dt><dt><span class="section"><a href="#id1168230285198">Build</a></span></dt><dt><span class="section"><a href="#id1168230285214">Install</a></span></dt><dt><span class="section"><a href="#id1168230285238">Install Hierarchy<
/a></span></dt></dl></dd></dl></div><div class="section" title="Building Requirements"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230284842"></a>Building Requirements</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Some operating systems have split their distribution packages into
a run-time and a development package. You will need to install
the development package versions, which include header files and
@@ -158,10 +158,6 @@
and deploying BIND 10 as an authoritative name server using
its defaults. For troubleshooting, full customizations and further
details, see the respective chapters in the BIND 10 guide.
- </p></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- The development prototype of the b10-auth server listens on
- 0.0.0.0 (all interfaces) port 5300. (This is not the standard
- domain service port.)
</p></div><p>
To quickly get started with BIND 10, follow these steps.
</p><div class="orderedlist"><ol class="orderedlist" type="1"><li class="listitem">
@@ -181,7 +177,7 @@
</p></li><li class="listitem"><p>Start the server:
</p><pre class="screen">$ <strong class="userinput"><code>/usr/local/sbin/bind10</code></strong></pre><p>
</p></li><li class="listitem"><p>Test it; for example:
- </p><pre class="screen">$ <strong class="userinput"><code>dig @127.0.0.1 -p 5300 -c CH -t TXT authors.bind</code></strong></pre><p>
+ </p><pre class="screen">$ <strong class="userinput"><code>dig @127.0.0.1 -c CH -t TXT authors.bind</code></strong></pre><p>
</p></li><li class="listitem"><p>Load desired zone file(s), for example:
</p><pre class="screen">$ <strong class="userinput"><code>b10-loadzone <em class="replaceable"><code>your.zone.example.org</code></em></code></strong></pre><p>
</p></li><li class="listitem">
@@ -192,14 +188,14 @@
the Git code revision control system or as a downloadable
tar file. It may also be available in pre-compiled ready-to-use
packages from operating system vendors.
- </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285028"></a>Download Tar File</h3></div></div></div><p>
+ </p><div class="section" title="Download Tar File"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285021"></a>Download Tar File</h3></div></div></div><p>
Downloading a release tar file is the recommended method to
obtain the source code.
</p><p>
The BIND 10 releases are available as tar file downloads from
<a class="ulink" href="ftp://ftp.isc.org/isc/bind10/" target="_top">ftp://ftp.isc.org/isc/bind10/</a>.
Periodic development snapshots may also be available.
- </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285047"></a>Retrieve from Git</h3></div></div></div><p>
+ </p></div><div class="section" title="Retrieve from Git"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285041"></a>Retrieve from Git</h3></div></div></div><p>
Downloading this "bleeding edge" code is recommended only for
developers or advanced users. Using development code in a production
environment is not recommended.
@@ -233,7 +229,7 @@
<span class="command"><strong>autoheader</strong></span>,
<span class="command"><strong>automake</strong></span>,
and related commands.
- </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285108"></a>Configure before the build</h3></div></div></div><p>
+ </p></div><div class="section" title="Configure before the build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285101"></a>Configure before the build</h3></div></div></div><p>
BIND 10 uses the GNU Build System to discover build environment
details.
To generate the makefiles using the defaults, simply run:
@@ -264,16 +260,16 @@
</p><p>
If the configure fails, it may be due to missing or old
dependencies.
- </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285205"></a>Build</h3></div></div></div><p>
+ </p></div><div class="section" title="Build"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285198"></a>Build</h3></div></div></div><p>
After the configure step is complete, to build the executables
from the C++ code and prepare the Python scripts, run:
</p><pre class="screen">$ <strong class="userinput"><code>make</code></strong></pre><p>
- </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285221"></a>Install</h3></div></div></div><p>
+ </p></div><div class="section" title="Install"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285214"></a>Install</h3></div></div></div><p>
To install the BIND 10 executables, support files,
and documentation, run:
</p><pre class="screen">$ <strong class="userinput"><code>make install</code></strong></pre><p>
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285244"></a>Install Hierarchy</h3></div></div></div><p>
+ </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>The install step may require superuser privileges.</p></div></div><div class="section" title="Install Hierarchy"><div class="titlepage"><div><div><h3 class="title"><a name="id1168230285238"></a>Install Hierarchy</h3></div></div></div><p>
The following is the layout of the complete BIND 10 installation:
</p><div class="itemizedlist"><ul class="itemizedlist" type="disc"><li class="listitem">
<code class="filename">bin/</code> —
@@ -490,15 +486,12 @@ shutdown
the details and relays (over a <span class="command"><strong>b10-msgq</strong></span> command
channel) the configuration on to the specified module.
</p><p>
- </p></div><div class="chapter" title="Chapter 8. Authoritative Server"><div class="titlepage"><div><div><h2 class="title"><a name="authserver"></a>Chapter 8. Authoritative Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230285822">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285888">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285918">Loading Master Zones Files</a></span></dt></dl></div><p>
+ </p></div><div class="chapter" title="Chapter 8. Authoritative Server"><div class="titlepage"><div><div><h2 class="title"><a name="authserver"></a>Chapter 8. Authoritative Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230285812">Server Configurations</a></span></dt><dt><span class="section"><a href="#id1168230285877">Data Source Backends</a></span></dt><dt><span class="section"><a href="#id1168230285908">Loading Master Zones Files</a></span></dt></dl></div><p>
The <span class="command"><strong>b10-auth</strong></span> is the authoritative DNS server.
It supports EDNS0 and DNSSEC. It supports IPv6.
Normally it is started by the <span class="command"><strong>bind10</strong></span> master
process.
- </p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
- This development prototype release listens on all interfaces
- and the non-standard port 5300.
- </p></div><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285822"></a>Server Configurations</h2></div></div></div><p>
+ </p><div class="section" title="Server Configurations"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285812"></a>Server Configurations</h2></div></div></div><p>
<span class="command"><strong>b10-auth</strong></span> is configured via the
<span class="command"><strong>b10-cfgmgr</strong></span> configuration manager.
The module name is <span class="quote">“<span class="quote">Auth</span>”</span>.
@@ -518,7 +511,7 @@ This may be a temporary setting until then.
</p><div class="variablelist"><dl><dt><span class="term">shutdown</span></dt><dd>Stop the authoritative DNS server.
</dd></dl></div><p>
- </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285888"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
+ </p></div><div class="section" title="Data Source Backends"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285877"></a>Data Source Backends</h2></div></div></div><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
For the development prototype release, <span class="command"><strong>b10-auth</strong></span>
supports a SQLite3 data source backend and in-memory data source
backend.
@@ -532,7 +525,7 @@ This may be a temporary setting until then.
The default is <code class="filename">/usr/local/var/</code>.)
This data file location may be changed by defining the
<span class="quote">“<span class="quote">database_file</span>”</span> configuration.
- </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285918"></a>Loading Master Zones Files</h2></div></div></div><p>
+ </p></div><div class="section" title="Loading Master Zones Files"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230285908"></a>Loading Master Zones Files</h2></div></div></div><p>
RFC 1035 style DNS master zone files may imported
into a BIND 10 data source by using the
<span class="command"><strong>b10-loadzone</strong></span> utility.
@@ -562,10 +555,9 @@ This may be a temporary setting until then.
all records from that prior zone disappear and a whole new set
appears.
</p></div></div><div class="chapter" title="Chapter 9. Incoming Zone Transfers"><div class="titlepage"><div><div><h2 class="title"><a name="xfrin"></a>Chapter 9. Incoming Zone Transfers</h2></div></div></div><p>
- The <span class="command"><strong>b10-xfrin</strong></span> process is started by
- <span class="command"><strong>bind10</strong></span>.
- It can be manually triggered to request an AXFR zone
- transfer. When received, it is stored in the BIND 10
+ Incoming zones are transferred using the <span class="command"><strong>b10-xfrin</strong></span>
+ process which is started by <span class="command"><strong>bind10</strong></span>.
+ When received, the zone is stored in the BIND 10
data store, and its records can be served by
<span class="command"><strong>b10-auth</strong></span>.
In combination with <span class="command"><strong>b10-zonemgr</strong></span> (for
@@ -574,6 +566,9 @@ This may be a temporary setting until then.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
The current development release of BIND 10 only supports
AXFR. (IXFR is not supported.)
+
+
+
</p></div><p>
To manually trigger a zone transfer to retrieve a remote zone,
you may use the <span class="command"><strong>bindctl</strong></span> utility.
@@ -608,7 +603,7 @@ This may be a temporary setting until then.
</p><div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"><h3 class="title">Note</h3><p>
Access control (such as allowing notifies) is not yet provided.
The primary/secondary service is not yet complete.
- </p></div></div><div class="chapter" title="Chapter 12. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 12. Recursive Name Server</h2></div></div></div><p>
+ </p></div></div><div class="chapter" title="Chapter 12. Recursive Name Server"><div class="titlepage"><div><div><h2 class="title"><a name="resolverserver"></a>Chapter 12. Recursive Name Server</h2></div></div></div><div class="toc"><p><b>Table of Contents</b></p><dl><dt><span class="section"><a href="#id1168230286296">Forwarding</a></span></dt></dl></div><p>
The <span class="command"><strong>b10-resolver</strong></span> process is started by
<span class="command"><strong>bind10</strong></span>.
@@ -637,26 +632,27 @@ This may be a temporary setting until then.
> <strong class="userinput"><code>config set Resolver/listen_on [{ "address": "127.0.0.1", "port": 53 }]</code></strong>
> <strong class="userinput"><code>config commit</code></strong>
</pre><p>
- </p><p>
- To enable forwarding, the upstream address and port must be
- configured to forward queries to, such as:
+ </p><div class="section" title="Forwarding"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="id1168230286296"></a>Forwarding</h2></div></div></div><p>
- </p><pre class="screen">
+ To enable forwarding, the upstream address and port must be
+ configured to forward queries to, such as:
+
+ </p><pre class="screen">
> <strong class="userinput"><code>config set Resolver/forward_addresses [{ "address": "<em class="replaceable"><code>192.168.1.1</code></em>", "port": 53 }]</code></strong>
> <strong class="userinput"><code>config commit</code></strong>
</pre><p>
- (Replace <em class="replaceable"><code>192.168.1.1</code></em> to point to your
- full resolver.)
- </p><p>
- Normal iterative name service can be re-enabled by clearing the
- forwarding address(es); for example:
+ (Replace <em class="replaceable"><code>192.168.1.1</code></em> to point to your
+ full resolver.)
+ </p><p>
+ Normal iterative name service can be re-enabled by clearing the
+ forwarding address(es); for example:
- </p><pre class="screen">
+ </p><pre class="screen">
> <strong class="userinput"><code>config set Resolver/forward_addresses []</code></strong>
> <strong class="userinput"><code>config commit</code></strong>
</pre><p>
- </p></div><div class="chapter" title="Chapter 13. Statistics"><div class="titlepage"><div><div><h2 class="title"><a name="statistics"></a>Chapter 13. Statistics</h2></div></div></div><p>
+ </p></div></div><div class="chapter" title="Chapter 13. Statistics"><div class="titlepage"><div><div><h2 class="title"><a name="statistics"></a>Chapter 13. Statistics</h2></div></div></div><p>
The <span class="command"><strong>b10-stats</strong></span> process is started by
<span class="command"><strong>bind10</strong></span>.
It periodically collects statistics data from various modules
diff --git a/doc/guide/bind10-guide.xml b/doc/guide/bind10-guide.xml
index bceb40c..c020f11 100644
--- a/doc/guide/bind10-guide.xml
+++ b/doc/guide/bind10-guide.xml
@@ -1199,10 +1199,9 @@ TODO
<title>Incoming Zone Transfers</title>
<para>
- The <command>b10-xfrin</command> process is started by
- <command>bind10</command>.
- It can be manually triggered to request an AXFR zone
- transfer. When received, it is stored in the BIND 10
+ Incoming zones are transferred using the <command>b10-xfrin</command>
+ process which is started by <command>bind10</command>.
+ When received, the zone is stored in the BIND 10
data store, and its records can be served by
<command>b10-auth</command>.
In combination with <command>b10-zonemgr</command> (for
@@ -1213,8 +1212,22 @@ TODO
<note><simpara>
The current development release of BIND 10 only supports
AXFR. (IXFR is not supported.)
+
+<!-- TODO: sqlite3 data source only? -->
+
</simpara></note>
+<!-- TODO:
+
+how to tell bind10 you are a secondary?
+
+when will it first attempt to check for new zone? (using REFRESH?)
+what if zonemgr is not running?
+
+what if a NOTIFY is sent?
+
+-->
+
<para>
To manually trigger a zone transfer to retrieve a remote zone,
you may use the <command>bindctl</command> utility.
@@ -1223,6 +1236,9 @@ TODO
<screen>> <userinput>Xfrin retransfer zone_name="<option>foo.example.org</option>" master=<option>192.0.2.99</option></userinput></screen>
</para>
+<!-- TODO: can that retransfer be used to identify a new zone? -->
+<!-- TODO: what if doesn't exist at that master IP? -->
+
</chapter>
<chapter id="xfrout">
@@ -1329,28 +1345,34 @@ what is XfroutClient xfr_client??
<!-- TODO: later the above will have some defaults -->
- <para>
- To enable forwarding, the upstream address and port must be
- configured to forward queries to, such as:
+ <section>
+ <title>Forwarding</title>
- <screen>
+ <para>
+
+ To enable forwarding, the upstream address and port must be
+ configured to forward queries to, such as:
+
+ <screen>
> <userinput>config set Resolver/forward_addresses [{ "address": "<replaceable>192.168.1.1</replaceable>", "port": 53 }]</userinput>
> <userinput>config commit</userinput>
</screen>
- (Replace <replaceable>192.168.1.1</replaceable> to point to your
- full resolver.)
- </para>
+ (Replace <replaceable>192.168.1.1</replaceable> to point to your
+ full resolver.)
+ </para>
- <para>
- Normal iterative name service can be re-enabled by clearing the
- forwarding address(es); for example:
+ <para>
+ Normal iterative name service can be re-enabled by clearing the
+ forwarding address(es); for example:
- <screen>
+ <screen>
> <userinput>config set Resolver/forward_addresses []</userinput>
> <userinput>config commit</userinput>
</screen>
- </para>
+ </para>
+
+ </section>
<!-- TODO: later try this
diff --git a/ext/asio/asio/detail/epoll_reactor.hpp b/ext/asio/asio/detail/epoll_reactor.hpp
index 6367398..0a1eac0 100644
--- a/ext/asio/asio/detail/epoll_reactor.hpp
+++ b/ext/asio/asio/detail/epoll_reactor.hpp
@@ -207,7 +207,7 @@ public:
// Cancel all operations associated with the given descriptor. The
// handlers associated with the descriptor will be invoked with the
// operation_aborted error.
- void cancel_ops(socket_type descriptor, per_descriptor_data& descriptor_data)
+ void cancel_ops(socket_type, per_descriptor_data& descriptor_data)
{
mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);
diff --git a/ext/asio/asio/detail/kqueue_reactor.hpp b/ext/asio/asio/detail/kqueue_reactor.hpp
index 1e118b3..bfa004d 100644
--- a/ext/asio/asio/detail/kqueue_reactor.hpp
+++ b/ext/asio/asio/detail/kqueue_reactor.hpp
@@ -205,7 +205,7 @@ public:
// Cancel all operations associated with the given descriptor. The
// handlers associated with the descriptor will be invoked with the
// operation_aborted error.
- void cancel_ops(socket_type descriptor, per_descriptor_data& descriptor_data)
+ void cancel_ops(socket_type , per_descriptor_data& descriptor_data)
{
mutex::scoped_lock descriptor_lock(descriptor_data->mutex_);
diff --git a/ext/asio/asio/detail/null_thread.hpp b/ext/asio/asio/detail/null_thread.hpp
index d96883f..ce3d470 100644
--- a/ext/asio/asio/detail/null_thread.hpp
+++ b/ext/asio/asio/detail/null_thread.hpp
@@ -40,7 +40,7 @@ class null_thread
public:
// Constructor.
template <typename Function>
- null_thread(Function f)
+ null_thread(Function )
{
asio::system_error e(
asio::error::operation_not_supported, "thread");
diff --git a/src/bin/Makefile.am b/src/bin/Makefile.am
index 1768ce7..23d660c 100644
--- a/src/bin/Makefile.am
+++ b/src/bin/Makefile.am
@@ -1,4 +1,4 @@
SUBDIRS = bind10 bindctl cfgmgr loadzone msgq host cmdctl auth xfrin xfrout \
- usermgr zonemgr stats tests resolver
+ usermgr zonemgr stats tests resolver sockcreator
check-recursive: all-recursive
diff --git a/src/bin/auth/Makefile.am b/src/bin/auth/Makefile.am
index cdfc55e..dfe23ee 100644
--- a/src/bin/auth/Makefile.am
+++ b/src/bin/auth/Makefile.am
@@ -39,9 +39,9 @@ pkglibexec_PROGRAMS = b10-auth
b10_auth_SOURCES = query.cc query.h
b10_auth_SOURCES += auth_srv.cc auth_srv.h
b10_auth_SOURCES += change_user.cc change_user.h
-b10_auth_SOURCES += config.cc config.h
+b10_auth_SOURCES += auth_config.cc auth_config.h
b10_auth_SOURCES += command.cc command.h
-b10_auth_SOURCES += common.h
+b10_auth_SOURCES += common.h common.cc
b10_auth_SOURCES += statistics.cc statistics.h
b10_auth_SOURCES += main.cc
b10_auth_LDADD = $(top_builddir)/src/lib/datasrc/libdatasrc.la
diff --git a/src/bin/auth/auth_config.cc b/src/bin/auth/auth_config.cc
new file mode 100644
index 0000000..7929d80
--- /dev/null
+++ b/src/bin/auth/auth_config.cc
@@ -0,0 +1,347 @@
+// Copyright (C) 2010 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 <set>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <boost/foreach.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/name.h>
+#include <dns/rrclass.h>
+
+#include <cc/data.h>
+
+#include <datasrc/memory_datasrc.h>
+#include <datasrc/zonetable.h>
+
+#include <auth/auth_srv.h>
+#include <auth/auth_config.h>
+#include <auth/common.h>
+
+#include <server_common/portconfig.h>
+
+using namespace std;
+using boost::shared_ptr;
+using namespace isc::dns;
+using namespace isc::data;
+using namespace isc::datasrc;
+using namespace isc::server_common::portconfig;
+
+namespace {
+// Forward declaration
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id,
+ bool internal);
+
+/// A derived \c AuthConfigParser class for the "datasources" configuration
+/// identifier.
+class DatasourcesConfig : public AuthConfigParser {
+public:
+ DatasourcesConfig(AuthSrv& server) : server_(server) {}
+ virtual void build(ConstElementPtr config_value);
+ virtual void commit();
+private:
+ AuthSrv& server_;
+ vector<shared_ptr<AuthConfigParser> > datasources_;
+ set<string> configured_sources_;
+};
+
+void
+DatasourcesConfig::build(ConstElementPtr config_value) {
+ BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
+ // The caller is supposed to perform syntax-level checks, but we'll
+ // do minimum level of validation ourselves so that we won't crash due
+ // to a buggy application.
+ ConstElementPtr datasrc_type = datasrc_elem->get("type");
+ if (!datasrc_type) {
+ isc_throw(AuthConfigError, "Missing data source type");
+ }
+
+ if (configured_sources_.find(datasrc_type->stringValue()) !=
+ configured_sources_.end()) {
+ isc_throw(AuthConfigError, "Data source type '" <<
+ datasrc_type->stringValue() << "' already configured");
+ }
+
+ shared_ptr<AuthConfigParser> datasrc_config =
+ shared_ptr<AuthConfigParser>(
+ createAuthConfigParser(server_, string("datasources/") +
+ datasrc_type->stringValue(),
+ true));
+ datasrc_config->build(datasrc_elem);
+ datasources_.push_back(datasrc_config);
+
+ configured_sources_.insert(datasrc_type->stringValue());
+ }
+}
+
+void
+DatasourcesConfig::commit() {
+ // XXX a short term workaround: clear all data sources and then reset
+ // to new ones so that we can remove data sources that don't exist in
+ // the new configuration and have been used in the server.
+ // This could be inefficient and requires knowledge about
+ // server implementation details, and isn't scalable wrt the number of
+ // data source types, and should eventually be improved.
+ // Currently memory data source for class IN is the only possibility.
+ server_.setMemoryDataSrc(RRClass::IN(), AuthSrv::MemoryDataSrcPtr());
+
+ BOOST_FOREACH(shared_ptr<AuthConfigParser> datasrc_config, datasources_) {
+ datasrc_config->commit();
+ }
+}
+
+/// A derived \c AuthConfigParser class for the memory type datasource
+/// configuration. It does not correspond to the configuration syntax;
+/// it's instantiated for internal use.
+class MemoryDatasourceConfig : public AuthConfigParser {
+public:
+ MemoryDatasourceConfig(AuthSrv& server) :
+ server_(server),
+ rrclass_(0) // XXX: dummy initial value
+ {}
+ virtual void build(ConstElementPtr config_value);
+ virtual void commit() {
+ server_.setMemoryDataSrc(rrclass_, memory_datasrc_);
+ }
+private:
+ AuthSrv& server_;
+ RRClass rrclass_;
+ AuthSrv::MemoryDataSrcPtr memory_datasrc_;
+};
+
+void
+MemoryDatasourceConfig::build(ConstElementPtr config_value) {
+ // XXX: apparently we cannot retrieve the default RR class from the
+ // module spec. As a temporary workaround we hardcode the default value.
+ ConstElementPtr rrclass_elem = config_value->get("class");
+ rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
+
+ // We'd eventually optimize building zones (in case of reloading) by
+ // selectively loading fresh zones. Right now we simply check the
+ // RR class is supported by the server implementation.
+ server_.getMemoryDataSrc(rrclass_);
+ memory_datasrc_ = AuthSrv::MemoryDataSrcPtr(new MemoryDataSrc());
+
+ ConstElementPtr zones_config = config_value->get("zones");
+ if (!zones_config) {
+ // XXX: Like the RR class, we cannot retrieve the default value here,
+ // so we assume an empty zone list in this case.
+ return;
+ }
+
+ BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
+ ConstElementPtr origin = zone_config->get("origin");
+ if (!origin) {
+ isc_throw(AuthConfigError, "Missing zone origin");
+ }
+ ConstElementPtr file = zone_config->get("file");
+ if (!file) {
+ isc_throw(AuthConfigError, "Missing zone file for zone: "
+ << origin->str());
+ }
+ shared_ptr<MemoryZone> new_zone(new MemoryZone(rrclass_,
+ Name(origin->stringValue())));
+ const result::Result result = memory_datasrc_->addZone(new_zone);
+ if (result == result::EXIST) {
+ isc_throw(AuthConfigError, "zone "<< origin->str()
+ << " already exists");
+ }
+
+ /*
+ * TODO: Once we have better reloading of configuration (something
+ * else than throwing everything away and loading it again), we will
+ * need the load method to be split into some kind of build and
+ * commit/abort parts.
+ */
+ new_zone->load(file->stringValue());
+ }
+}
+
+/// A derived \c AuthConfigParser class for the "statistics-internal"
+/// configuration identifier.
+class StatisticsIntervalConfig : public AuthConfigParser {
+public:
+ StatisticsIntervalConfig(AuthSrv& server) :
+ server_(server), interval_(0)
+ {}
+ virtual void build(ConstElementPtr config_value) {
+ const int32_t config_interval = config_value->intValue();
+ if (config_interval < 0) {
+ isc_throw(AuthConfigError, "Negative statistics interval value: "
+ << config_interval);
+ }
+ if (config_interval > 86400) {
+ isc_throw(AuthConfigError, "Statistics interval value "
+ << config_interval
+ << " must be equal to or shorter than 86400");
+ }
+ interval_ = config_interval;
+ }
+ virtual void commit() {
+ // setStatisticsTimerInterval() is not 100% exception free. But
+ // exceptions should happen only in a very rare situation, so we
+ // let them be thrown and subsequently regard them as a fatal error.
+ server_.setStatisticsTimerInterval(interval_);
+ }
+private:
+ AuthSrv& server_;
+ uint32_t interval_;
+};
+
+/// A special parser for testing: it throws from commit() despite the
+/// suggested convention of the class interface.
+class ThrowerCommitConfig : public AuthConfigParser {
+public:
+ virtual void build(ConstElementPtr) {} // ignore param, do nothing
+ virtual void commit() {
+ throw 10;
+ }
+};
+
+/**
+ * \brief Configuration parser for listen_on.
+ *
+ * It parses and sets the listening addresses of the server.
+ *
+ * It acts in unusual way. Since actually binding (changing) the sockets
+ * is an operation that is expected to throw often, it shouldn't happen
+ * in commit. Thefere we do it in build. But if the config is not committed
+ * then, we would have it wrong. So we store the old addresses and if
+ * commit is not called before destruction of the object, we return the
+ * old addresses (which is the same kind of dangerous operation, but it is
+ * expected that if we just managed to bind some and had the old ones binded
+ * before, it should work).
+ *
+ * We might do something better in future (like open only the ports that are
+ * extra, put them in in commit and close the old ones), but that's left out
+ * for now.
+ */
+class ListenAddressConfig : public AuthConfigParser {
+public:
+ ListenAddressConfig(AuthSrv& server) :
+ server_(server)
+ { }
+ ~ ListenAddressConfig() {
+ if (rollbackAddresses_.get() != NULL) {
+ server_.setListenAddresses(*rollbackAddresses_);
+ }
+ }
+private:
+ typedef auto_ptr<AddressList> AddrListPtr;
+public:
+ virtual void build(ConstElementPtr config) {
+ AddressList newAddresses = parseAddresses(config, "listen_on");
+ AddrListPtr old(new AddressList(server_.getListenAddresses()));
+ server_.setListenAddresses(newAddresses);
+ /*
+ * Set the rollback addresses only after successful setting of the
+ * new addresses, so we don't try to rollback if the setup is
+ * unsuccessful (the above can easily throw).
+ */
+ rollbackAddresses_ = old;
+ }
+ virtual void commit() {
+ rollbackAddresses_.release();
+ }
+private:
+ AuthSrv& server_;
+ /**
+ * This is the old address list, if we expect to roll back. When we commit,
+ * this is set to NULL.
+ */
+ AddrListPtr rollbackAddresses_;
+};
+
+// This is a generalized version of create function that can create
+// an AuthConfigParser object for "internal" use.
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id,
+ bool internal)
+{
+ // For the initial implementation we use a naive if-else blocks for
+ // simplicity. In future we'll probably generalize it using map-like
+ // data structure, and may even provide external register interface so
+ // that it can be dynamically customized.
+ if (config_id == "datasources") {
+ return (new DatasourcesConfig(server));
+ } else if (config_id == "statistics-interval") {
+ return (new StatisticsIntervalConfig(server));
+ } else if (internal && config_id == "datasources/memory") {
+ return (new MemoryDatasourceConfig(server));
+ } else if (config_id == "listen_on") {
+ return (new ListenAddressConfig(server));
+ } else if (config_id == "_commit_throw") {
+ // This is for testing purpose only and should not appear in the
+ // actual configuration syntax. While this could crash the caller
+ // as a result, the server implementation is expected to perform
+ // syntax level validation and should be safe in practice. In future,
+ // we may introduce dynamic registration of configuration parsers,
+ // and then this test can be done in a cleaner and safer way.
+ return (new ThrowerCommitConfig());
+ } else {
+ isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
+ config_id);
+ }
+}
+} // end of unnamed namespace
+
+AuthConfigParser*
+createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
+ return (createAuthConfigParser(server, config_id, false));
+}
+
+void
+configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
+ if (!config_set) {
+ isc_throw(AuthConfigError,
+ "Null pointer is passed to configuration parser");
+ }
+
+ typedef shared_ptr<AuthConfigParser> ParserPtr;
+ vector<ParserPtr> parsers;
+ typedef pair<string, ConstElementPtr> ConfigPair;
+ try {
+ BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
+ // We should eventually integrate the sqlite3 DB configuration to
+ // this framework, but to minimize diff we begin with skipping that
+ // part.
+ if (config_pair.first == "database_file") {
+ continue;
+ }
+
+ ParserPtr parser(createAuthConfigParser(server,
+ config_pair.first));
+ parser->build(config_pair.second);
+ parsers.push_back(parser);
+ }
+ } catch (const AuthConfigError& ex) {
+ throw; // simply rethrowing it
+ } catch (const isc::Exception& ex) {
+ isc_throw(AuthConfigError, "Server configuration failed: " <<
+ ex.what());
+ }
+
+ try {
+ BOOST_FOREACH(ParserPtr parser, parsers) {
+ parser->commit();
+ }
+ } catch (...) {
+ throw FatalError("Unrecoverable error: "
+ "a configuration parser threw in commit");
+ }
+}
diff --git a/src/bin/auth/auth_config.h b/src/bin/auth/auth_config.h
new file mode 100644
index 0000000..6f18810
--- /dev/null
+++ b/src/bin/auth/auth_config.h
@@ -0,0 +1,202 @@
+// Copyright (C) 2010 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 <string>
+
+#include <exceptions/exceptions.h>
+
+#include <cc/data.h>
+
+#ifndef __CONFIG_H
+#define __CONFIG_H 1
+
+class AuthSrv;
+
+/// An exception that is thrown if an error occurs while configuring an
+/// \c AuthSrv object.
+class AuthConfigError : public isc::Exception {
+public:
+ AuthConfigError(const char* file, size_t line, const char* what) :
+ isc::Exception(file, line, what) {}
+};
+
+/// The abstract base class that represents a single configuration identifier
+/// for an \c AuthSrv object.
+///
+/// In general, each top level configuration identifier for \c AuthSrv is
+/// expected to have its own derived class of this base class.
+/// For example, for the following configuration:
+/// \code { "param1": 10, "param2": { "subparam1": "foo", "subparam2": [] } }
+/// \endcode
+/// "param1" and "param2" are top level identifiers, and would correspond to
+/// derived \c AuthConfigParser classes.
+/// "subparam1" and/or "subparam2" may also have dedicated derived classes.
+///
+/// These derived classes are hidden inside the implementation; applications
+/// are not expected to (and in fact cannot) instantiate them directly.
+///
+/// Each derived class is generally expected to be constructed with an
+/// \c AuthSrv object to be configured and hold a reference to the server
+/// throughout the configuration process.
+/// For each derived class, the \c build() method parses the configuration
+/// value for the corresponding identifier and prepares new configuration
+/// value(s) to be applied to the server. This method may throw an exception
+/// when it encounters an error.
+/// The \c commit() method actually applies the new configuration value
+/// to the server. It's basically not expected to throw an exception;
+/// any configuration operations that can fail (such as ones involving
+/// resource allocation) should be done in \c build().
+///
+/// When the destructor is called before \c commit(), the destructor is
+/// supposed to make sure the state of the \c AuthSrv object is the same
+/// as that before it starts building the configuration value.
+/// If \c build() doesn't change the server state (which is recommended)
+/// the destructor doesn't have to do anything special in this regard.
+/// This is a key to ensure the strong exception guarantee (see also
+/// the description of \c configureAuthServer()).
+class AuthConfigParser {
+ ///
+ /// \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:
+ AuthConfigParser(const AuthConfigParser& source);
+ AuthConfigParser& operator=(const AuthConfigParser& 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).
+ AuthConfigParser() {}
+public:
+ /// The destructor.
+ virtual ~AuthConfigParser() {}
+ //@}
+
+ /// 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.
+ /// In the above example, the derived class for the identifier "param1"
+ /// would be passed an data \c Element storing an integer whose value
+ /// is 10, and would record that value internally;
+ /// the derived class for the identifier "param2" would be passed a
+ /// map element and (after parsing) convert it into some internal
+ /// data structure.
+ ///
+ /// 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. 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;
+
+ /// 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 configureAuthServer(), 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;
+};
+
+/// Configure an \c AuthSrv object with a set of configuration values.
+///
+/// This function parses configuration information stored in \c config_set
+/// and configures the \c server by applying the configuration to it.
+/// It provides the strong exception guarantee as long as the underlying
+/// derived class implementations of \c AuthConfigParser meet the assumption,
+/// that is, it ensures that either configuration is fully applied or the
+/// state of the server is intact.
+///
+/// If a syntax or semantics level error happens during the configuration
+/// (such as malformed configuration or invalid configuration parameter),
+/// this function throws an exception of class \c AuthConfigError.
+/// 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 AuthConfigError.
+/// 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.
+///
+/// \param server The \c AuthSrv object to be configured.
+/// \param config_set A JSON style configuration to apply to \c server.
+void configureAuthServer(AuthSrv& server,
+ isc::data::ConstElementPtr config_set);
+
+/// Create a new \c AuthConfigParser object for a given configuration
+/// identifier.
+///
+/// It internally identifies an appropriate derived class for the given
+/// identifier and creates a new instance of that class. The caller can
+/// then configure the \c server regarding the identifier by calling
+/// the \c build() and \c commit() methods of the returned object.
+///
+/// In practice, this function is only expected to be used as a backend of
+/// \c configureAuthServer() and is not supposed to be called directly
+/// by applications. It is publicly available mainly for testing purposes.
+/// When called directly, the created object must be deleted by the caller.
+/// Note: this means if this module and the caller use incompatible sets of
+/// new/delete, it may cause unexpected strange failure. We could avoid that
+/// by providing a separate deallocation function or by using a smart pointer,
+/// but since the expected usage of this function is very limited (i.e. for
+/// our own testing purposes) it would be an overkilling. We therefore prefer
+/// simplicity and keeping the interface intuitive.
+///
+/// If the resource allocation for the new object fails, a corresponding
+/// standard exception will be thrown. Otherwise this function is not
+/// expected to throw an exception, unless the constructor of the underlying
+/// derived class implementation (unexpectedly) throws.
+///
+/// \param server The \c AuthSrv object to be configured.
+/// \param config_id The configuration identifier for which a parser object
+/// is to be created.
+/// \return A pointer to an \c AuthConfigParser object.
+AuthConfigParser* createAuthConfigParser(AuthSrv& server,
+ const std::string& config_id);
+
+#endif // __CONFIG_H
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/bin/auth/auth_srv.cc b/src/bin/auth/auth_srv.cc
index f46752a..9a49cc7 100644
--- a/src/bin/auth/auth_srv.cc
+++ b/src/bin/auth/auth_srv.cc
@@ -52,7 +52,7 @@
#include <xfr/xfrout_client.h>
#include <auth/common.h>
-#include <auth/config.h>
+#include <auth/auth_config.h>
#include <auth/auth_srv.h>
#include <auth/query.h>
#include <auth/statistics.h>
diff --git a/src/bin/auth/auth_srv.h b/src/bin/auth/auth_srv.h
index 8253c85..8a6d522 100644
--- a/src/bin/auth/auth_srv.h
+++ b/src/bin/auth/auth_srv.h
@@ -23,6 +23,15 @@
#include <cc/data.h>
#include <config/ccsession.h>
+#include <dns/message.h>
+#include <dns/buffer.h>
+
+#include <asiolink/io_message.h>
+#include <asiolink/io_service.h>
+#include <asiolink/dns_server.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/simple_callback.h>
#include <asiolink/asiolink.h>
#include <server_common/portconfig.h>
diff --git a/src/bin/auth/benchmarks/Makefile.am b/src/bin/auth/benchmarks/Makefile.am
index 3078dd5..c7de8d4 100644
--- a/src/bin/auth/benchmarks/Makefile.am
+++ b/src/bin/auth/benchmarks/Makefile.am
@@ -10,7 +10,7 @@ noinst_PROGRAMS = query_bench
query_bench_SOURCES = query_bench.cc
query_bench_SOURCES += ../query.h ../query.cc
query_bench_SOURCES += ../auth_srv.h ../auth_srv.cc
-query_bench_SOURCES += ../config.h ../config.cc
+query_bench_SOURCES += ../auth_config.h ../auth_config.cc
query_bench_SOURCES += ../statistics.h ../statistics.cc
query_bench_LDADD = $(top_builddir)/src/lib/dns/libdns++.la
diff --git a/src/bin/auth/benchmarks/query_bench.cc b/src/bin/auth/benchmarks/query_bench.cc
index 5e69134..86d9813 100644
--- a/src/bin/auth/benchmarks/query_bench.cc
+++ b/src/bin/auth/benchmarks/query_bench.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 <stdlib.h>
#include <iostream>
@@ -31,7 +33,7 @@
#include <xfr/xfrout_client.h>
#include <auth/auth_srv.h>
-#include <auth/config.h>
+#include <auth/auth_config.h>
#include <auth/query.h>
#include <asiolink/asiolink.h>
diff --git a/src/bin/auth/common.cc b/src/bin/auth/common.cc
new file mode 100644
index 0000000..35381a1
--- /dev/null
+++ b/src/bin/auth/common.cc
@@ -0,0 +1,36 @@
+// Copyright (C) 2009-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 <auth/common.h>
+#include <auth/spec_config.h>
+#include <stdlib.h>
+
+using std::string;
+
+string getXfroutSocketPath() {
+ if (getenv("B10_FROM_BUILD") != NULL) {
+ if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) {
+ return (string(getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) +
+ "/auth_xfrout_conn");
+ } else {
+ return (string(getenv("B10_FROM_BUILD")) + "/auth_xfrout_conn");
+ }
+ } else {
+ if (getenv("BIND10_XFROUT_SOCKET_FILE")) {
+ return (getenv("BIND10_XFROUT_SOCKET_FILE"));
+ } else {
+ return (UNIX_SOCKET_FILE);
+ }
+ }
+}
diff --git a/src/bin/auth/common.h b/src/bin/auth/common.h
index 6af09fb..b913593 100644
--- a/src/bin/auth/common.h
+++ b/src/bin/auth/common.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-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
@@ -29,6 +29,15 @@ public:
{}
};
+/// \short Get the path of socket to talk to xfrout
+///
+/// It takes some environment variables into account (B10_FROM_BUILD,
+/// B10_FROM_SOURCE_LOCALSTATEDIR and BIND10_XFROUT_SOCKET_FILE). It
+/// also considers the installation prefix.
+///
+/// The logic should be the same as in b10-xfrout, so they find each other.
+std::string getXfroutSocketPath();
+
#endif // __COMMON_H
// Local Variables:
diff --git a/src/bin/auth/config.cc b/src/bin/auth/config.cc
deleted file mode 100644
index f289ca0..0000000
--- a/src/bin/auth/config.cc
+++ /dev/null
@@ -1,347 +0,0 @@
-// Copyright (C) 2010 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 <set>
-#include <string>
-#include <utility>
-#include <vector>
-
-#include <boost/foreach.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/name.h>
-#include <dns/rrclass.h>
-
-#include <cc/data.h>
-
-#include <datasrc/memory_datasrc.h>
-#include <datasrc/zonetable.h>
-
-#include <auth/auth_srv.h>
-#include <auth/config.h>
-#include <auth/common.h>
-
-#include <server_common/portconfig.h>
-
-using namespace std;
-using boost::shared_ptr;
-using namespace isc::dns;
-using namespace isc::data;
-using namespace isc::datasrc;
-using namespace isc::server_common::portconfig;
-
-namespace {
-// Forward declaration
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
- bool internal);
-
-/// A derived \c AuthConfigParser class for the "datasources" configuration
-/// identifier.
-class DatasourcesConfig : public AuthConfigParser {
-public:
- DatasourcesConfig(AuthSrv& server) : server_(server) {}
- virtual void build(ConstElementPtr config_value);
- virtual void commit();
-private:
- AuthSrv& server_;
- vector<shared_ptr<AuthConfigParser> > datasources_;
- set<string> configured_sources_;
-};
-
-void
-DatasourcesConfig::build(ConstElementPtr config_value) {
- BOOST_FOREACH(ConstElementPtr datasrc_elem, config_value->listValue()) {
- // The caller is supposed to perform syntax-level checks, but we'll
- // do minimum level of validation ourselves so that we won't crash due
- // to a buggy application.
- ConstElementPtr datasrc_type = datasrc_elem->get("type");
- if (!datasrc_type) {
- isc_throw(AuthConfigError, "Missing data source type");
- }
-
- if (configured_sources_.find(datasrc_type->stringValue()) !=
- configured_sources_.end()) {
- isc_throw(AuthConfigError, "Data source type '" <<
- datasrc_type->stringValue() << "' already configured");
- }
-
- shared_ptr<AuthConfigParser> datasrc_config =
- shared_ptr<AuthConfigParser>(
- createAuthConfigParser(server_, string("datasources/") +
- datasrc_type->stringValue(),
- true));
- datasrc_config->build(datasrc_elem);
- datasources_.push_back(datasrc_config);
-
- configured_sources_.insert(datasrc_type->stringValue());
- }
-}
-
-void
-DatasourcesConfig::commit() {
- // XXX a short term workaround: clear all data sources and then reset
- // to new ones so that we can remove data sources that don't exist in
- // the new configuration and have been used in the server.
- // This could be inefficient and requires knowledge about
- // server implementation details, and isn't scalable wrt the number of
- // data source types, and should eventually be improved.
- // Currently memory data source for class IN is the only possibility.
- server_.setMemoryDataSrc(RRClass::IN(), AuthSrv::MemoryDataSrcPtr());
-
- BOOST_FOREACH(shared_ptr<AuthConfigParser> datasrc_config, datasources_) {
- datasrc_config->commit();
- }
-}
-
-/// A derived \c AuthConfigParser class for the memory type datasource
-/// configuration. It does not correspond to the configuration syntax;
-/// it's instantiated for internal use.
-class MemoryDatasourceConfig : public AuthConfigParser {
-public:
- MemoryDatasourceConfig(AuthSrv& server) :
- server_(server),
- rrclass_(0) // XXX: dummy initial value
- {}
- virtual void build(ConstElementPtr config_value);
- virtual void commit() {
- server_.setMemoryDataSrc(rrclass_, memory_datasrc_);
- }
-private:
- AuthSrv& server_;
- RRClass rrclass_;
- AuthSrv::MemoryDataSrcPtr memory_datasrc_;
-};
-
-void
-MemoryDatasourceConfig::build(ConstElementPtr config_value) {
- // XXX: apparently we cannot retrieve the default RR class from the
- // module spec. As a temporary workaround we hardcode the default value.
- ConstElementPtr rrclass_elem = config_value->get("class");
- rrclass_ = RRClass(rrclass_elem ? rrclass_elem->stringValue() : "IN");
-
- // We'd eventually optimize building zones (in case of reloading) by
- // selectively loading fresh zones. Right now we simply check the
- // RR class is supported by the server implementation.
- server_.getMemoryDataSrc(rrclass_);
- memory_datasrc_ = AuthSrv::MemoryDataSrcPtr(new MemoryDataSrc());
-
- ConstElementPtr zones_config = config_value->get("zones");
- if (!zones_config) {
- // XXX: Like the RR class, we cannot retrieve the default value here,
- // so we assume an empty zone list in this case.
- return;
- }
-
- BOOST_FOREACH(ConstElementPtr zone_config, zones_config->listValue()) {
- ConstElementPtr origin = zone_config->get("origin");
- if (!origin) {
- isc_throw(AuthConfigError, "Missing zone origin");
- }
- ConstElementPtr file = zone_config->get("file");
- if (!file) {
- isc_throw(AuthConfigError, "Missing zone file for zone: "
- << origin->str());
- }
- shared_ptr<MemoryZone> new_zone(new MemoryZone(rrclass_,
- Name(origin->stringValue())));
- const result::Result result = memory_datasrc_->addZone(new_zone);
- if (result == result::EXIST) {
- isc_throw(AuthConfigError, "zone "<< origin->str()
- << " already exists");
- }
-
- /*
- * TODO: Once we have better reloading of configuration (something
- * else than throwing everything away and loading it again), we will
- * need the load method to be split into some kind of build and
- * commit/abort parts.
- */
- new_zone->load(file->stringValue());
- }
-}
-
-/// A derived \c AuthConfigParser class for the "statistics-internal"
-/// configuration identifier.
-class StatisticsIntervalConfig : public AuthConfigParser {
-public:
- StatisticsIntervalConfig(AuthSrv& server) :
- server_(server), interval_(0)
- {}
- virtual void build(ConstElementPtr config_value) {
- const int32_t config_interval = config_value->intValue();
- if (config_interval < 0) {
- isc_throw(AuthConfigError, "Negative statistics interval value: "
- << config_interval);
- }
- if (config_interval > 86400) {
- isc_throw(AuthConfigError, "Statistics interval value "
- << config_interval
- << " must be equal to or shorter than 86400");
- }
- interval_ = config_interval;
- }
- virtual void commit() {
- // setStatisticsTimerInterval() is not 100% exception free. But
- // exceptions should happen only in a very rare situation, so we
- // let them be thrown and subsequently regard them as a fatal error.
- server_.setStatisticsTimerInterval(interval_);
- }
-private:
- AuthSrv& server_;
- uint32_t interval_;
-};
-
-/// A special parser for testing: it throws from commit() despite the
-/// suggested convention of the class interface.
-class ThrowerCommitConfig : public AuthConfigParser {
-public:
- virtual void build(ConstElementPtr) {} // ignore param, do nothing
- virtual void commit() {
- throw 10;
- }
-};
-
-/**
- * \brief Configuration parser for listen_on.
- *
- * It parses and sets the listening addresses of the server.
- *
- * It acts in unusual way. Since actually binding (changing) the sockets
- * is an operation that is expected to throw often, it shouldn't happen
- * in commit. Thefere we do it in build. But if the config is not committed
- * then, we would have it wrong. So we store the old addresses and if
- * commit is not called before destruction of the object, we return the
- * old addresses (which is the same kind of dangerous operation, but it is
- * expected that if we just managed to bind some and had the old ones binded
- * before, it should work).
- *
- * We might do something better in future (like open only the ports that are
- * extra, put them in in commit and close the old ones), but that's left out
- * for now.
- */
-class ListenAddressConfig : public AuthConfigParser {
-public:
- ListenAddressConfig(AuthSrv& server) :
- server_(server)
- { }
- ~ ListenAddressConfig() {
- if (rollbackAddresses_.get() != NULL) {
- server_.setListenAddresses(*rollbackAddresses_);
- }
- }
-private:
- typedef auto_ptr<AddressList> AddrListPtr;
-public:
- virtual void build(ConstElementPtr config) {
- AddressList newAddresses = parseAddresses(config, "listen_on");
- AddrListPtr old(new AddressList(server_.getListenAddresses()));
- server_.setListenAddresses(newAddresses);
- /*
- * Set the rollback addresses only after successful setting of the
- * new addresses, so we don't try to rollback if the setup is
- * unsuccessful (the above can easily throw).
- */
- rollbackAddresses_ = old;
- }
- virtual void commit() {
- rollbackAddresses_.release();
- }
-private:
- AuthSrv& server_;
- /**
- * This is the old address list, if we expect to roll back. When we commit,
- * this is set to NULL.
- */
- AddrListPtr rollbackAddresses_;
-};
-
-// This is a generalized version of create function that can create
-// an AuthConfigParser object for "internal" use.
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id,
- bool internal)
-{
- // For the initial implementation we use a naive if-else blocks for
- // simplicity. In future we'll probably generalize it using map-like
- // data structure, and may even provide external register interface so
- // that it can be dynamically customized.
- if (config_id == "datasources") {
- return (new DatasourcesConfig(server));
- } else if (config_id == "statistics-interval") {
- return (new StatisticsIntervalConfig(server));
- } else if (internal && config_id == "datasources/memory") {
- return (new MemoryDatasourceConfig(server));
- } else if (config_id == "listen_on") {
- return (new ListenAddressConfig(server));
- } else if (config_id == "_commit_throw") {
- // This is for testing purpose only and should not appear in the
- // actual configuration syntax. While this could crash the caller
- // as a result, the server implementation is expected to perform
- // syntax level validation and should be safe in practice. In future,
- // we may introduce dynamic registration of configuration parsers,
- // and then this test can be done in a cleaner and safer way.
- return (new ThrowerCommitConfig());
- } else {
- isc_throw(AuthConfigError, "Unknown configuration identifier: " <<
- config_id);
- }
-}
-} // end of unnamed namespace
-
-AuthConfigParser*
-createAuthConfigParser(AuthSrv& server, const std::string& config_id) {
- return (createAuthConfigParser(server, config_id, false));
-}
-
-void
-configureAuthServer(AuthSrv& server, ConstElementPtr config_set) {
- if (!config_set) {
- isc_throw(AuthConfigError,
- "Null pointer is passed to configuration parser");
- }
-
- typedef shared_ptr<AuthConfigParser> ParserPtr;
- vector<ParserPtr> parsers;
- typedef pair<string, ConstElementPtr> ConfigPair;
- try {
- BOOST_FOREACH(ConfigPair config_pair, config_set->mapValue()) {
- // We should eventually integrate the sqlite3 DB configuration to
- // this framework, but to minimize diff we begin with skipping that
- // part.
- if (config_pair.first == "database_file") {
- continue;
- }
-
- ParserPtr parser(createAuthConfigParser(server,
- config_pair.first));
- parser->build(config_pair.second);
- parsers.push_back(parser);
- }
- } catch (const AuthConfigError& ex) {
- throw; // simply rethrowing it
- } catch (const isc::Exception& ex) {
- isc_throw(AuthConfigError, "Server configuration failed: " <<
- ex.what());
- }
-
- try {
- BOOST_FOREACH(ParserPtr parser, parsers) {
- parser->commit();
- }
- } catch (...) {
- throw FatalError("Unrecoverable error: "
- "a configuration parser threw in commit");
- }
-}
diff --git a/src/bin/auth/config.h b/src/bin/auth/config.h
deleted file mode 100644
index 6f18810..0000000
--- a/src/bin/auth/config.h
+++ /dev/null
@@ -1,202 +0,0 @@
-// Copyright (C) 2010 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 <string>
-
-#include <exceptions/exceptions.h>
-
-#include <cc/data.h>
-
-#ifndef __CONFIG_H
-#define __CONFIG_H 1
-
-class AuthSrv;
-
-/// An exception that is thrown if an error occurs while configuring an
-/// \c AuthSrv object.
-class AuthConfigError : public isc::Exception {
-public:
- AuthConfigError(const char* file, size_t line, const char* what) :
- isc::Exception(file, line, what) {}
-};
-
-/// The abstract base class that represents a single configuration identifier
-/// for an \c AuthSrv object.
-///
-/// In general, each top level configuration identifier for \c AuthSrv is
-/// expected to have its own derived class of this base class.
-/// For example, for the following configuration:
-/// \code { "param1": 10, "param2": { "subparam1": "foo", "subparam2": [] } }
-/// \endcode
-/// "param1" and "param2" are top level identifiers, and would correspond to
-/// derived \c AuthConfigParser classes.
-/// "subparam1" and/or "subparam2" may also have dedicated derived classes.
-///
-/// These derived classes are hidden inside the implementation; applications
-/// are not expected to (and in fact cannot) instantiate them directly.
-///
-/// Each derived class is generally expected to be constructed with an
-/// \c AuthSrv object to be configured and hold a reference to the server
-/// throughout the configuration process.
-/// For each derived class, the \c build() method parses the configuration
-/// value for the corresponding identifier and prepares new configuration
-/// value(s) to be applied to the server. This method may throw an exception
-/// when it encounters an error.
-/// The \c commit() method actually applies the new configuration value
-/// to the server. It's basically not expected to throw an exception;
-/// any configuration operations that can fail (such as ones involving
-/// resource allocation) should be done in \c build().
-///
-/// When the destructor is called before \c commit(), the destructor is
-/// supposed to make sure the state of the \c AuthSrv object is the same
-/// as that before it starts building the configuration value.
-/// If \c build() doesn't change the server state (which is recommended)
-/// the destructor doesn't have to do anything special in this regard.
-/// This is a key to ensure the strong exception guarantee (see also
-/// the description of \c configureAuthServer()).
-class AuthConfigParser {
- ///
- /// \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:
- AuthConfigParser(const AuthConfigParser& source);
- AuthConfigParser& operator=(const AuthConfigParser& 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).
- AuthConfigParser() {}
-public:
- /// The destructor.
- virtual ~AuthConfigParser() {}
- //@}
-
- /// 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.
- /// In the above example, the derived class for the identifier "param1"
- /// would be passed an data \c Element storing an integer whose value
- /// is 10, and would record that value internally;
- /// the derived class for the identifier "param2" would be passed a
- /// map element and (after parsing) convert it into some internal
- /// data structure.
- ///
- /// 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. 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;
-
- /// 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 configureAuthServer(), 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;
-};
-
-/// Configure an \c AuthSrv object with a set of configuration values.
-///
-/// This function parses configuration information stored in \c config_set
-/// and configures the \c server by applying the configuration to it.
-/// It provides the strong exception guarantee as long as the underlying
-/// derived class implementations of \c AuthConfigParser meet the assumption,
-/// that is, it ensures that either configuration is fully applied or the
-/// state of the server is intact.
-///
-/// If a syntax or semantics level error happens during the configuration
-/// (such as malformed configuration or invalid configuration parameter),
-/// this function throws an exception of class \c AuthConfigError.
-/// 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 AuthConfigError.
-/// 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.
-///
-/// \param server The \c AuthSrv object to be configured.
-/// \param config_set A JSON style configuration to apply to \c server.
-void configureAuthServer(AuthSrv& server,
- isc::data::ConstElementPtr config_set);
-
-/// Create a new \c AuthConfigParser object for a given configuration
-/// identifier.
-///
-/// It internally identifies an appropriate derived class for the given
-/// identifier and creates a new instance of that class. The caller can
-/// then configure the \c server regarding the identifier by calling
-/// the \c build() and \c commit() methods of the returned object.
-///
-/// In practice, this function is only expected to be used as a backend of
-/// \c configureAuthServer() and is not supposed to be called directly
-/// by applications. It is publicly available mainly for testing purposes.
-/// When called directly, the created object must be deleted by the caller.
-/// Note: this means if this module and the caller use incompatible sets of
-/// new/delete, it may cause unexpected strange failure. We could avoid that
-/// by providing a separate deallocation function or by using a smart pointer,
-/// but since the expected usage of this function is very limited (i.e. for
-/// our own testing purposes) it would be an overkilling. We therefore prefer
-/// simplicity and keeping the interface intuitive.
-///
-/// If the resource allocation for the new object fails, a corresponding
-/// standard exception will be thrown. Otherwise this function is not
-/// expected to throw an exception, unless the constructor of the underlying
-/// derived class implementation (unexpectedly) throws.
-///
-/// \param server The \c AuthSrv object to be configured.
-/// \param config_id The configuration identifier for which a parser object
-/// is to be created.
-/// \return A pointer to an \c AuthConfigParser object.
-AuthConfigParser* createAuthConfigParser(AuthSrv& server,
- const std::string& config_id);
-
-#endif // __CONFIG_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/bin/auth/main.cc b/src/bin/auth/main.cc
index 0701b94..64a8325 100644
--- a/src/bin/auth/main.cc
+++ b/src/bin/auth/main.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2009 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2009-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
@@ -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 <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
@@ -37,7 +39,7 @@
#include <auth/spec_config.h>
#include <auth/common.h>
-#include <auth/config.h>
+#include <auth/auth_config.h>
#include <auth/command.h>
#include <auth/change_user.h>
#include <auth/auth_srv.h>
@@ -120,19 +122,7 @@ main(int argc, char* argv[]) {
bool xfrin_session_established = false; // XXX (see Trac #287)
bool statistics_session_established = false; // XXX (see Trac #287)
ModuleCCSession* config_session = NULL;
- string xfrout_socket_path;
- if (getenv("B10_FROM_BUILD") != NULL) {
- if (getenv("B10_FROM_SOURCE_LOCALSTATEDIR")) {
- xfrout_socket_path = string("B10_FROM_SOURCE_LOCALSTATEDIR") +
- "/auth_xfrout_conn";
- } else {
- xfrout_socket_path = string(getenv("B10_FROM_BUILD")) +
- "/auth_xfrout_conn";
- }
- } else {
- xfrout_socket_path = UNIX_SOCKET_FILE;
- }
- XfroutClient xfrout_client(xfrout_socket_path);
+ XfroutClient xfrout_client(getXfroutSocketPath());
try {
string specfile;
if (getenv("B10_FROM_BUILD")) {
@@ -163,10 +153,6 @@ main(int argc, char* argv[]) {
my_command_handler);
cout << "[b10-auth] Configuration channel established." << endl;
- if (uid != NULL) {
- changeUser(uid);
- }
-
xfrin_session = new Session(io_service.get_io_service());
cout << "[b10-auth] Xfrin session channel created." << endl;
xfrin_session->establish(NULL);
@@ -190,6 +176,10 @@ main(int argc, char* argv[]) {
configureAuthServer(*auth_server, config_session->getFullConfig());
auth_server->updateConfig(ElementPtr());
+ if (uid != NULL) {
+ changeUser(uid);
+ }
+
cout << "[b10-auth] Server started." << endl;
io_service.run();
diff --git a/src/bin/auth/query.cc b/src/bin/auth/query.cc
index e936c97..323f890 100644
--- a/src/bin/auth/query.cc
+++ b/src/bin/auth/query.cc
@@ -210,6 +210,8 @@ Query::process() const {
// into answer section.
BOOST_FOREACH(RRsetPtr rrset, *target) {
response_.addRRset(Message::SECTION_ANSWER, rrset);
+ // Handle additional for answer section
+ getAdditional(*result.zone, *rrset.get());
}
} else {
response_.addRRset(Message::SECTION_ANSWER,
diff --git a/src/bin/auth/tests/Makefile.am b/src/bin/auth/tests/Makefile.am
index 7d489a1..882f7fd 100644
--- a/src/bin/auth/tests/Makefile.am
+++ b/src/bin/auth/tests/Makefile.am
@@ -1,4 +1,5 @@
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
AM_CPPFLAGS += -I$(top_builddir)/src/lib/dns -I$(top_srcdir)/src/bin
AM_CPPFLAGS += -I$(top_builddir)/src/lib/cc
AM_CPPFLAGS += $(BOOST_INCLUDES)
@@ -16,18 +17,23 @@ CLEANFILES = *.gcno *.gcda
TESTS =
if HAVE_GTEST
+
+BUILT_SOURCES = ../spec_config.h
TESTS += run_unittests
run_unittests_SOURCES = $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += ../auth_srv.h ../auth_srv.cc
run_unittests_SOURCES += ../query.h ../query.cc
run_unittests_SOURCES += ../change_user.h ../change_user.cc
-run_unittests_SOURCES += ../config.h ../config.cc
+run_unittests_SOURCES += ../auth_config.h ../auth_config.cc
run_unittests_SOURCES += ../command.h ../command.cc
+run_unittests_SOURCES += ../common.h ../common.cc
+run_unittests_SOURCES += ../spec_config.h
run_unittests_SOURCES += ../statistics.h ../statistics.cc
run_unittests_SOURCES += auth_srv_unittest.cc
run_unittests_SOURCES += config_unittest.cc
run_unittests_SOURCES += command_unittest.cc
+run_unittests_SOURCES += common_unittest.cc
run_unittests_SOURCES += query_unittest.cc
run_unittests_SOURCES += change_user_unittest.cc
run_unittests_SOURCES += statistics_unittest.cc
diff --git a/src/bin/auth/tests/command_unittest.cc b/src/bin/auth/tests/command_unittest.cc
index f788d9e..dd1f6eb 100644
--- a/src/bin/auth/tests/command_unittest.cc
+++ b/src/bin/auth/tests/command_unittest.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 <cassert>
#include <cstdlib>
#include <string>
@@ -32,7 +34,7 @@
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
-#include <auth/config.h>
+#include <auth/auth_config.h>
#include <auth/command.h>
#include <asiolink/asiolink.h>
diff --git a/src/bin/auth/tests/common_unittest.cc b/src/bin/auth/tests/common_unittest.cc
new file mode 100644
index 0000000..9b18142
--- /dev/null
+++ b/src/bin/auth/tests/common_unittest.cc
@@ -0,0 +1,96 @@
+// 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 <auth/common.h>
+#include <auth/spec_config.h>
+#include <vector>
+#include <string>
+#include <cstdio>
+#include <boost/foreach.hpp>
+
+using std::pair;
+using std::vector;
+using std::string;
+
+namespace {
+
+class Paths : public ::testing::Test {
+private:
+ typedef pair<string, string*> Environ;
+ vector<Environ> restoreEnviron;
+public:
+ void TearDown() {
+ // Restore the original environment
+ BOOST_FOREACH(const Environ &env, restoreEnviron) {
+ if (env.second == NULL) {
+ EXPECT_EQ(0, unsetenv(env.first.c_str())) <<
+ "Couldn't restore environment, results of other tests"
+ "are uncertain";
+ } else {
+ EXPECT_EQ(0, setenv(env.first.c_str(), env.second->c_str(),
+ 1)) << "Couldn't restore environment, "
+ "results of other tests are uncertain";
+ }
+ }
+ }
+protected:
+ // Sets a temporary value into environment. If value is empty, it deletes
+ // the variable from environment (just for simplicity).
+ void setEnv(const string& name, const string& value) {
+ // Backup the original environment
+ char* env(getenv(name.c_str()));
+ restoreEnviron.push_back(Environ(name, env == NULL ? NULL :
+ new string(env)));
+ // Set the new value
+ if (value.empty()) {
+ EXPECT_EQ(0, unsetenv(name.c_str()));
+ } else {
+ EXPECT_EQ(0, setenv(name.c_str(), value.c_str(), 1));
+ }
+ }
+ // Test getXfroutSocketPath under given environment
+ void testXfrout(const string& fromBuild, const string& localStateDir,
+ const string& socketFile, const string& expected)
+ {
+ setEnv("B10_FROM_BUILD", fromBuild);
+ setEnv("B10_FROM_SOURCE_LOCALSTATEDIR", localStateDir);
+ setEnv("BIND10_XFROUT_SOCKET_FILE", socketFile);
+ EXPECT_EQ(expected, getXfroutSocketPath());
+ }
+};
+
+// Test that when we have no special environment, we get the default from prefix
+TEST_F(Paths, xfroutNoEnv) {
+ testXfrout("", "", "", UNIX_SOCKET_FILE);
+}
+
+// Override by B10_FROM_BUILD
+TEST_F(Paths, xfroutFromBuild) {
+ testXfrout("/from/build", "", "/wrong/path",
+ "/from/build/auth_xfrout_conn");
+}
+
+// Override by B10_FROM_SOURCE_LOCALSTATEDIR
+TEST_F(Paths, xfroutLocalStatedir) {
+ testXfrout("/wrong/path", "/state/dir", "/wrong/path",
+ "/state/dir/auth_xfrout_conn");
+}
+
+// Override by BIND10_XFROUT_SOCKET_FILE explicitly
+TEST_F(Paths, xfroutFromEnv) {
+ testXfrout("", "", "/the/path/to/file", "/the/path/to/file");
+}
+
+}
diff --git a/src/bin/auth/tests/config_unittest.cc b/src/bin/auth/tests/config_unittest.cc
index 8cce0af..9c94e25 100644
--- a/src/bin/auth/tests/config_unittest.cc
+++ b/src/bin/auth/tests/config_unittest.cc
@@ -26,7 +26,7 @@
#include <xfr/xfrout_client.h>
#include <auth/auth_srv.h>
-#include <auth/config.h>
+#include <auth/auth_config.h>
#include <auth/common.h>
#include <testutils/mockups.h>
diff --git a/src/bin/auth/tests/query_unittest.cc b/src/bin/auth/tests/query_unittest.cc
index 05dd748..c68b672 100644
--- a/src/bin/auth/tests/query_unittest.cc
+++ b/src/bin/auth/tests/query_unittest.cc
@@ -341,12 +341,20 @@ TEST_F(QueryTest, apexAnyMatch) {
// in the answer section from the additional.
EXPECT_NO_THROW(Query(memory_datasrc, Name("example.com"),
RRType::ANY(), response).process());
- responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 0,
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 4, 0, 3,
"example.com. 3600 IN SOA . . 0 0 0 0 0\n"
"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",
- NULL, NULL, mock_zone->getOrigin());
+ NULL, ns_addrs_txt, mock_zone->getOrigin());
+}
+
+TEST_F(QueryTest, mxANYMatch) {
+ EXPECT_NO_THROW(Query(memory_datasrc, Name("mx.example.com"),
+ RRType::ANY(), response).process());
+ responseCheck(response, Rcode::NOERROR(), AA_FLAG, 3, 3, 4,
+ mx_txt, zone_ns_txt,
+ (string(ns_addrs_txt) + string(www_a_txt)).c_str());
}
TEST_F(QueryTest, glueANYMatch) {
diff --git a/src/bin/bind10/bind10.8 b/src/bin/bind10/bind10.8
index a75136b..b275f2d 100644
--- a/src/bin/bind10/bind10.8
+++ b/src/bin/bind10/bind10.8
@@ -22,7 +22,7 @@
bind10 \- BIND 10 boss process
.SH "SYNOPSIS"
.HP \w'\fBbind10\fR\ 'u
-\fBbind10\fR [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-verbose\fR]
+\fBbind10\fR [\fB\-m\ \fR\fB\fIfile\fR\fR] [\fB\-n\fR] [\fB\-u\ \fR\fB\fIuser\fR\fR] [\fB\-v\fR] [\fB\-\-msgq\-socket\-file\ \fR\fB\fIfile\fR\fR] [\fB\-\-no\-cache\fR] [\fB\-\-user\ \fR\fB\fIuser\fR\fR] [\fB\-\-pretty\-name\ \fR\fB\fIname\fR\fR] [\fB\-\-brittle\fR] [\fB\-\-verbose\fR]
.SH "DESCRIPTION"
.PP
The
@@ -66,6 +66,14 @@ or
\fBbind10\fR\&.
.RE
.PP
+\fB\-\-brittle\fR
+.RS 4
+Shutdown if any of the child processes of
+\fBbind10\fR
+exit\&. This is intended to help developers debug the server, and should
+not be used in production.
+.RE
+.PP
\fB\-v\fR, \fB\-\-verbose\fR
.RS 4
Display more about what is going on for
diff --git a/src/bin/bind10/bind10.py.in b/src/bin/bind10/bind10.py.in
index ce6e523..e6504e1 100755
--- a/src/bin/bind10/bind10.py.in
+++ b/src/bin/bind10/bind10.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010,2011 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
@@ -139,7 +139,8 @@ class ProcessInfo:
self.restart_schedule = RestartSchedule()
self.uid = uid
self.username = username
- self._spawn()
+ self.process = None
+ self.pid = None
def _preexec_work(self):
"""Function used before running a program that needs to run as a
@@ -186,6 +187,11 @@ class ProcessInfo:
self.pid = self.process.pid
self.restart_schedule.set_run_start_time()
+ # spawn() and respawn() are the same for now, but in the future they
+ # may have different functionality
+ def spawn(self):
+ self._spawn()
+
def respawn(self):
self._spawn()
@@ -194,14 +200,21 @@ class CChannelConnectError(Exception): pass
class BoB:
"""Boss of BIND class."""
- def __init__(self, msgq_socket_file=None, nocache=False, verbose=False,
- setuid=None, username=None):
+ def __init__(self, msgq_socket_file=None, data_path=None,
+ config_filename=None, nocache=False, verbose=False, setuid=None,
+ username=None, cmdctl_port=None, brittle=False):
"""
Initialize the Boss of BIND. This is a singleton (only one can run).
The msgq_socket_file specifies the UNIX domain socket file that the
msgq process listens on. If verbose is True, then the boss reports
what it is doing.
+
+ Data path and config filename are passed trough to config manager
+ (if provided) and specify the config file to be used.
+
+ The cmdctl_port is passed to cmdctl and specify on which port it
+ should listen.
"""
self.cc_session = None
self.ccs = None
@@ -219,6 +232,10 @@ class BoB:
self.uid = setuid
self.username = username
self.verbose = verbose
+ self.data_path = data_path
+ self.config_filename = config_filename
+ self.cmdctl_port = cmdctl_port
+ self.brittle = brittle
def config_handler(self, new_config):
# If this is initial update, don't do anything now, leave it to startup
@@ -270,9 +287,17 @@ class BoB:
answer = isc.config.ccsession.create_answer(0)
return answer
+ def get_processes(self):
+ pids = list(self.processes.keys())
+ pids.sort()
+ process_list = [ ]
+ for pid in pids:
+ process_list.append([pid, self.processes[pid].name])
+ return process_list
+
def command_handler(self, command, args):
if self.verbose:
- sys.stdout.write("[bind10] Boss got command: " + command + "\n")
+ sys.stdout.write("[bind10] Boss got command: " + str(command) + "\n")
answer = isc.config.ccsession.create_answer(1, "command not implemented")
if type(command) != str:
answer = isc.config.ccsession.create_answer(1, "bad command")
@@ -280,8 +305,22 @@ class BoB:
if command == "shutdown":
self.runnable = False
answer = isc.config.ccsession.create_answer(0)
+ elif command == "sendstats":
+ # send statistics data to the stats daemon immediately
+ cmd = isc.config.ccsession.create_command(
+ 'set', { "stats_data": {
+ 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+ }})
+ seq = self.cc_session.group_sendmsg(cmd, 'Stats')
+ self.cc_session.group_recvmsg(True, seq)
+ answer = isc.config.ccsession.create_answer(0)
+ elif command == "ping":
+ answer = isc.config.ccsession.create_answer(0, "pong")
+ elif command == "show_processes":
+ answer = isc.config.ccsession. \
+ create_answer(0, self.get_processes())
else:
- answer = isc.config.ccsession.create_answer(1,
+ answer = isc.config.ccsession.create_answer(1,
"Unknown command")
return answer
@@ -369,6 +408,7 @@ class BoB:
c_channel = ProcessInfo("b10-msgq", ["b10-msgq"], c_channel_env,
True, not self.verbose, uid=self.uid,
username=self.username)
+ c_channel.spawn()
self.processes[c_channel.pid] = c_channel
self.log_started(c_channel.pid)
@@ -390,9 +430,15 @@ class BoB:
Starts the configuration manager process
"""
self.log_starting("b10-cfgmgr")
- bind_cfgd = ProcessInfo("b10-cfgmgr", ["b10-cfgmgr"],
+ args = ["b10-cfgmgr"]
+ if self.data_path is not None:
+ args.append("--data-path=" + self.data_path)
+ if self.config_filename is not None:
+ args.append("--config-filename=" + self.config_filename)
+ bind_cfgd = ProcessInfo("b10-cfgmgr", args,
c_channel_env, uid=self.uid,
username=self.username)
+ bind_cfgd.spawn()
self.processes[bind_cfgd.pid] = bind_cfgd
self.log_started(bind_cfgd.pid)
@@ -427,6 +473,7 @@ class BoB:
"""
self.log_starting(name, port, address)
newproc = ProcessInfo(name, args, c_channel_env)
+ newproc.spawn()
self.processes[newproc.pid] = newproc
self.log_started(newproc.pid)
@@ -500,8 +547,13 @@ class BoB:
self.start_simple("b10-stats", c_channel_env)
def start_cmdctl(self, c_channel_env):
- # XXX: we hardcode port 8080
- self.start_simple("b10-cmdctl", c_channel_env, 8080)
+ """
+ Starts the command control process
+ """
+ args = ["b10-cmdctl"]
+ if self.cmdctl_port is not None:
+ args.append("--port=" + str(self.cmdctl_port))
+ self.start_process("b10-cmdctl", args, c_channel_env, self.cmdctl_port)
def start_all_processes(self):
"""
@@ -668,20 +720,22 @@ class BoB:
if self.verbose:
sys.stdout.write("[bind10] All processes ended, server done.\n")
+ def _get_process_exit_status(self):
+ return os.waitpid(-1, os.WNOHANG)
+
def reap_children(self):
"""Check to see if any of our child processes have exited,
and note this for later handling.
"""
while True:
try:
- (pid, exit_status) = os.waitpid(-1, os.WNOHANG)
+ (pid, exit_status) = self._get_process_exit_status()
except OSError as o:
if o.errno == errno.ECHILD: break
# XXX: should be impossible to get any other error here
raise
if pid == 0: break
if pid in self.processes:
-
# One of the processes we know about. Get information on it.
proc_info = self.processes.pop(pid)
proc_info.restart_schedule.set_run_stop_time()
@@ -705,6 +759,11 @@ class BoB:
sys.stdout.write(
"[bind10] The b10-msgq process died, shutting down.\n")
self.runnable = False
+
+ # If we're in 'brittle' mode, we want to shutdown after
+ # any process dies.
+ if self.brittle:
+ self.runnable = False
else:
sys.stdout.write("[bind10] Unknown child pid %d exited.\n" % pid)
@@ -785,6 +844,52 @@ def process_rename(option, opt_str, value, parser):
"""Function that renames the process if it is requested by a option."""
isc.util.process.rename(value)
+def parse_args(args=sys.argv[1:], Parser=OptionParser):
+ """
+ Function for parsing command line arguments. Returns the
+ options object from OptionParser.
+ """
+ parser = Parser(version=VERSION)
+ parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
+ type="string", default=None,
+ help="UNIX domain socket file the b10-msgq daemon will use")
+ parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
+ default=False, help="disable hot-spot cache in authoritative DNS server")
+ parser.add_option("-u", "--user", dest="user", type="string", default=None,
+ help="Change user after startup (must run as root)")
+ parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
+ help="display more about what is going on")
+ parser.add_option("--pretty-name", type="string", action="callback",
+ callback=process_rename,
+ help="Set the process name (displayed in ps, top, ...)")
+ parser.add_option("-c", "--config-file", action="store",
+ dest="config_file", default=None,
+ help="Configuration database filename")
+ parser.add_option("-p", "--data-path", dest="data_path",
+ help="Directory to search for configuration files",
+ default=None)
+ parser.add_option("--cmdctl-port", dest="cmdctl_port", type="int",
+ default=None, help="Port of command control")
+ parser.add_option("--pid-file", dest="pid_file", type="string",
+ default=None,
+ help="file to dump the PID of the BIND 10 process")
+ parser.add_option("--brittle", dest="brittle", action="store_true",
+ help="debugging flag: exit if any component dies")
+
+ (options, args) = parser.parse_args(args)
+
+ if options.cmdctl_port is not None:
+ try:
+ isc.net.parse.port_parse(options.cmdctl_port)
+ except ValueError as e:
+ parser.error(e)
+
+ if args:
+ parser.print_help()
+ sys.exit(1)
+
+ return options
+
def dump_pid(pid_file):
"""
Dump the PID of the current process to the specified file. If the given
@@ -814,33 +919,14 @@ def unlink_pid_file(pid_file):
if error.errno is not errno.ENOENT:
raise
+
def main():
global options
global boss_of_bind
# Enforce line buffering on stdout, even when not a TTY
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), line_buffering=True)
- # Parse any command-line options.
- parser = OptionParser(version=VERSION)
- parser.add_option("-m", "--msgq-socket-file", dest="msgq_socket_file",
- type="string", default=None,
- help="UNIX domain socket file the b10-msgq daemon will use")
- parser.add_option("-n", "--no-cache", action="store_true", dest="nocache",
- default=False, help="disable hot-spot cache in authoritative DNS server")
- parser.add_option("-u", "--user", dest="user", type="string", default=None,
- help="Change user after startup (must run as root)")
- parser.add_option("-v", "--verbose", dest="verbose", action="store_true",
- help="display more about what is going on")
- parser.add_option("--pretty-name", type="string", action="callback",
- callback=process_rename,
- help="Set the process name (displayed in ps, top, ...)")
- parser.add_option("--pid-file", dest="pid_file", type="string",
- default=None,
- help="file to dump the PID of the BIND 10 process")
- (options, args) = parser.parse_args()
- if args:
- parser.print_help()
- sys.exit(1)
+ options = parse_args()
# Check user ID.
setuid = None
@@ -890,8 +976,9 @@ def main():
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
# Go bob!
- boss_of_bind = BoB(options.msgq_socket_file, options.nocache,
- options.verbose, setuid, username)
+ boss_of_bind = BoB(options.msgq_socket_file, options.data_path,
+ options.config_file, options.nocache, options.verbose,
+ setuid, username, options.cmdctl_port, options.brittle)
startup_result = boss_of_bind.startup()
if startup_result:
sys.stderr.write("[bind10] Error on startup: %s\n" % startup_result)
@@ -899,17 +986,6 @@ def main():
sys.stdout.write("[bind10] BIND 10 started\n")
dump_pid(options.pid_file)
- # send "bind10.boot_time" to b10-stats
- time.sleep(1) # wait a second
- if options.verbose:
- sys.stdout.write("[bind10] send \"bind10.boot_time\" to b10-stats\n")
- cmd = isc.config.ccsession.create_command('set',
- { "stats_data": {
- 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
- }
- })
- boss_of_bind.cc_session.group_sendmsg(cmd, 'Stats')
-
# In our main loop, we check for dead processes or messages
# on the c-channel.
wakeup_fd = wakeup_pipe[0]
diff --git a/src/bin/bind10/bind10.xml b/src/bin/bind10/bind10.xml
index f3964a6..6331503 100644
--- a/src/bin/bind10/bind10.xml
+++ b/src/bin/bind10/bind10.xml
@@ -48,6 +48,8 @@
<arg><option>-n</option></arg>
<arg><option>-u <replaceable>user</replaceable></option></arg>
<arg><option>-v</option></arg>
+ <arg><option>-c<replaceable>config-filename</replaceable></option></arg>
+ <arg><option>-p<replaceable>data_path</replaceable></option></arg>
<arg><option>--msgq-socket-file <replaceable>file</replaceable></option></arg>
<arg><option>--no-cache</option></arg>
<arg><option>--user <replaceable>user</replaceable></option></arg>
@@ -80,6 +82,31 @@
<para>The arguments are as follows:</para>
<variablelist>
+ <varlistentry>
+ <term>
+ <option>-c</option><replaceable>config-filename</replaceable>,
+ <option>--config-file</option> <replaceable>config-filename</replaceable>
+ </term>
+ <listitem>
+ <para>The configuration filename to use. Can be either absolute or
+ relative to data path. In case it is absolute, value of data path is
+ not considered.</para>
+ <para>Defaults to b10-config.db.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-p</option><replaceable>data-path</replaceable>,
+ <option>--data-path</option> <replaceable>data-path</replaceable>
+ </term>
+ <listitem>
+ <para>The path where BIND 10 programs look for various data files.
+ Currently only b10-cfgmgr uses it to locate the configuration file,
+ but the usage might be extended for other programs and other types
+ of files.</para>
+ </listitem>
+ </varlistentry>
<varlistentry>
<term><option>-m</option> <replaceable>file</replaceable>,
@@ -145,6 +172,9 @@ The default is the basename of ARG 0.
</refsect1>
<!--
+TODO: configuration section
+-->
+<!--
<refsect1>
<title>FILES</title>
<para><filename></filename>
diff --git a/src/bin/bind10/bob.spec b/src/bin/bind10/bob.spec
index b45a12f..1184fd1 100644
--- a/src/bin/bind10/bob.spec
+++ b/src/bin/bind10/bob.spec
@@ -21,6 +21,21 @@
"command_name": "shutdown",
"command_description": "Shut down BIND 10",
"command_args": []
+ },
+ {
+ "command_name": "sendstats",
+ "command_description": "Send data to a statistics module at once",
+ "command_args": []
+ },
+ {
+ "command_name": "ping",
+ "command_description": "Ping the boss process",
+ "command_args": []
+ },
+ {
+ "command_name": "show_processes",
+ "command_description": "List the running BIND 10 processes",
+ "command_args": []
}
]
}
diff --git a/src/bin/bind10/tests/Makefile.am b/src/bin/bind10/tests/Makefile.am
index be2c091..34d809a 100644
--- a/src/bin/bind10/tests/Makefile.am
+++ b/src/bin/bind10/tests/Makefile.am
@@ -1,5 +1,6 @@
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
#PYTESTS = args_test.py bind10_test.py
+# NOTE: this has a generated test found in the builddir
PYTESTS = bind10_test.py
EXTRA_DIST = $(PYTESTS)
@@ -13,5 +14,6 @@ endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
env PYTHONPATH=$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/bin/bind10 \
- $(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
+ BIND10_MSGQ_SOCKET_FILE=$(abs_top_builddir)/msgq_socket \
+ $(PYCOVERAGE_RUN) $(abs_builddir)/$$pytest || exit ; \
done
diff --git a/src/bin/bind10/tests/bind10_test.in b/src/bin/bind10/tests/bind10_test.in
deleted file mode 100755
index cbd7452..0000000
--- a/src/bin/bind10/tests/bind10_test.in
+++ /dev/null
@@ -1,32 +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
-
-BIND10_PATH=@abs_top_srcdir@/src/bin/bind10
-
-PATH=@abs_top_srcdir@/src/bin/msgq:@abs_top_srcdir@/src/bin/auth:@abs_top_srcdir@/src/bin/bind-cfgd:$PATH
-export PATH
-
-PYTHONPATH=@abs_top_srcdir@/src/lib/python:@abs_top_srcdir@/src/bin/bind10
-export PYTHONPATH
-
-cd ${BIND10_PATH}/tests
-${PYTHON_EXEC} -O bind10_test.py $*
-exec ${PYTHON_EXEC} -O args_test.py $*
-
diff --git a/src/bin/bind10/tests/bind10_test.py.in b/src/bin/bind10/tests/bind10_test.py.in
index 0603443..8432c89 100644
--- a/src/bin/bind10/tests/bind10_test.py.in
+++ b/src/bin/bind10/tests/bind10_test.py.in
@@ -1,4 +1,19 @@
-from bind10 import ProcessInfo, BoB, dump_pid, unlink_pid_file
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from bind10 import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
# XXX: environment tests are currently disabled, due to the preprocessor
# setup that we have now complicating the environment
@@ -9,6 +24,10 @@ import os
import signal
import socket
from isc.net.addr import IPAddr
+import time
+import isc
+
+from isc.testutils.parse_args import TestOptParser, OptsError
class TestProcessInfo(unittest.TestCase):
def setUp(self):
@@ -30,6 +49,7 @@ class TestProcessInfo(unittest.TestCase):
def test_init(self):
pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ pi.spawn()
os.dup2(self.old_stdout, sys.stdout.fileno())
self.assertEqual(pi.name, 'Test Process')
self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
@@ -50,12 +70,14 @@ class TestProcessInfo(unittest.TestCase):
def test_setting_null_stdout(self):
pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
dev_null_stdout=True)
+ pi.spawn()
os.dup2(self.old_stdout, sys.stdout.fileno())
self.assertEqual(pi.dev_null_stdout, True)
self.assertEqual(os.read(self.pipes[0], 100), b"")
def test_respawn(self):
pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
+ pi.spawn()
# wait for old process to work...
self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
# respawn it
@@ -104,17 +126,54 @@ class TestBoB(unittest.TestCase):
self.assertEqual(bob.cfg_start_auth, True)
self.assertEqual(bob.cfg_start_resolver, False)
-# Class for testing the BoB start/stop components routines.
+ def test_command_handler(self):
+ class DummySession():
+ def group_sendmsg(self, msg, group):
+ (self.msg, self.group) = (msg, group)
+ def group_recvmsg(self, nonblock, seq): pass
+ bob = BoB()
+ bob.verbose = True
+ bob.cc_session = DummySession()
+ # a bad command
+ self.assertEqual(bob.command_handler(-1, None),
+ isc.config.ccsession.create_answer(1, "bad command"))
+ # "shutdown" command
+ self.assertEqual(bob.command_handler("shutdown", None),
+ isc.config.ccsession.create_answer(0))
+ self.assertFalse(bob.runnable)
+ # "sendstats" command
+ self.assertEqual(bob.command_handler("sendstats", None),
+ isc.config.ccsession.create_answer(0))
+ self.assertEqual(bob.cc_session.group, "Stats")
+ self.assertEqual(bob.cc_session.msg,
+ isc.config.ccsession.create_command(
+ 'set', { "stats_data": {
+ 'bind10.boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
+ }}))
+ # "ping" command
+ self.assertEqual(bob.command_handler("ping", None),
+ isc.config.ccsession.create_answer(0, "pong"))
+ # "show_processes" command
+ self.assertEqual(bob.command_handler("show_processes", None),
+ isc.config.ccsession.create_answer(0,
+ bob.get_processes()))
+ # an unknown command
+ self.assertEqual(bob.command_handler("__UNKNOWN__", None),
+ isc.config.ccsession.create_answer(1, "Unknown command"))
+
+# Class for testing the BoB without actually starting processes.
+# This is used for testing the start/stop components routines and
+# the BoB commands.
#
-# Although testing that external processes start is outside the scope
+# Testing that external processes start is outside the scope
# of the unit test, by overriding the process start methods we can check
# that the right processes are started depending on the configuration
# options.
-class StartStopCheckBob(BoB):
+class MockBob(BoB):
def __init__(self):
BoB.__init__(self)
-# Set flags as to which of the overridden methods has been run.
+ # Set flags as to which of the overridden methods has been run.
self.msgq = False
self.cfgmgr = False
self.ccsession = False
@@ -126,6 +185,7 @@ class StartStopCheckBob(BoB):
self.stats = False
self.cmdctl = False
self.c_channel_env = {}
+ self.processes = { }
def read_bind10_config(self):
# Configuration options are set directly
@@ -133,65 +193,105 @@ class StartStopCheckBob(BoB):
def start_msgq(self, c_channel_env):
self.msgq = True
+ self.processes[2] = ProcessInfo('b10-msgq', ['/bin/false'])
+ self.processes[2].pid = 2
def start_cfgmgr(self, c_channel_env):
self.cfgmgr = True
+ self.processes[3] = ProcessInfo('b10-cfgmgr', ['/bin/false'])
+ self.processes[3].pid = 3
def start_ccsession(self, c_channel_env):
self.ccsession = True
+ self.processes[4] = ProcessInfo('b10-ccsession', ['/bin/false'])
+ self.processes[4].pid = 4
def start_auth(self, c_channel_env):
self.auth = True
+ self.processes[5] = ProcessInfo('b10-auth', ['/bin/false'])
+ self.processes[5].pid = 5
def start_resolver(self, c_channel_env):
self.resolver = True
+ self.processes[6] = ProcessInfo('b10-resolver', ['/bin/false'])
+ self.processes[6].pid = 6
def start_xfrout(self, c_channel_env):
self.xfrout = True
+ self.processes[7] = ProcessInfo('b10-xfrout', ['/bin/false'])
+ self.processes[7].pid = 7
def start_xfrin(self, c_channel_env):
self.xfrin = True
+ self.processes[8] = ProcessInfo('b10-xfrin', ['/bin/false'])
+ self.processes[8].pid = 8
def start_zonemgr(self, c_channel_env):
self.zonemgr = True
+ self.processes[9] = ProcessInfo('b10-zonemgr', ['/bin/false'])
+ self.processes[9].pid = 9
def start_stats(self, c_channel_env):
self.stats = True
+ self.processes[10] = ProcessInfo('b10-stats', ['/bin/false'])
+ self.processes[10].pid = 10
def start_cmdctl(self, c_channel_env):
self.cmdctl = True
+ self.processes[11] = ProcessInfo('b10-cmdctl', ['/bin/false'])
+ self.processes[11].pid = 11
# We don't really use all of these stop_ methods. But it might turn out
# someone would add some stop_ method to BoB and we want that one overriden
# in case he forgets to update the tests.
def stop_msgq(self):
+ if self.msgq:
+ del self.processes[2]
self.msgq = False
def stop_cfgmgr(self):
+ if self.cfgmgr:
+ del self.processes[3]
self.cfgmgr = False
def stop_ccsession(self):
+ if self.ccssession:
+ del self.processes[4]
self.ccsession = False
def stop_auth(self):
+ if self.auth:
+ del self.processes[5]
self.auth = False
def stop_resolver(self):
+ if self.resolver:
+ del self.processes[6]
self.resolver = False
def stop_xfrout(self):
+ if self.xfrout:
+ del self.processes[7]
self.xfrout = False
def stop_xfrin(self):
+ if self.xfrin:
+ del self.processes[8]
self.xfrin = False
def stop_zonemgr(self):
+ if self.zonemgr:
+ del self.processes[9]
self.zonemgr = False
def stop_stats(self):
+ if self.stats:
+ del self.processes[10]
self.stats = False
def stop_cmdctl(self):
+ if self.cmdctl:
+ del self.processes[11]
self.cmdctl = False
class TestStartStopProcessesBob(unittest.TestCase):
@@ -251,7 +351,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
# is specified.
def test_start_none(self):
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes and check what was started
@@ -264,7 +364,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
# Checks the processes started when starting only the auth process
def test_start_auth(self):
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes and check what was started
@@ -278,7 +378,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
# Checks the processes started when starting only the resolver process
def test_start_resolver(self):
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes and check what was started
@@ -292,7 +392,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
# Checks the processes started when starting both auth and resolver process
def test_start_both(self):
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes and check what was started
@@ -310,7 +410,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
"""
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes (nothing much should be started, as in
@@ -375,7 +475,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
Tests that a process is started only once.
"""
# Create BoB and ensure correct initialization
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
# Start processes (both)
@@ -401,7 +501,7 @@ class TestStartStopProcessesBob(unittest.TestCase):
Test that processes are not started by the config handler before
startup.
"""
- bob = StartStopCheckBob()
+ bob = MockBob()
self.check_preconditions(bob)
bob.start_auth = lambda: self.fail("Started auth again")
@@ -412,6 +512,101 @@ class TestStartStopProcessesBob(unittest.TestCase):
bob.config_handler({'start_auth': True, 'start_resolver': True})
+class TestBossCmd(unittest.TestCase):
+ def test_ping(self):
+ """
+ Confirm simple ping command works.
+ """
+ bob = MockBob()
+ answer = bob.command_handler("ping", None)
+ self.assertEqual(answer, {'result': [0, 'pong']})
+
+ def test_show_processes(self):
+ """
+ Confirm getting a list of processes works.
+ """
+ bob = MockBob()
+ answer = bob.command_handler("show_processes", None)
+ self.assertEqual(answer, {'result': [0, []]})
+
+ def test_show_processes_started(self):
+ """
+ Confirm getting a list of processes works.
+ """
+ bob = MockBob()
+ bob.start_all_processes()
+ answer = bob.command_handler("show_processes", None)
+ processes = [[2, 'b10-msgq'],
+ [3, 'b10-cfgmgr'],
+ [4, 'b10-ccsession'],
+ [5, 'b10-auth'],
+ [7, 'b10-xfrout'],
+ [8, 'b10-xfrin'],
+ [9, 'b10-zonemgr'],
+ [10, 'b10-stats'],
+ [11, 'b10-cmdctl']]
+ self.assertEqual(answer, {'result': [0, processes]})
+
+class TestParseArgs(unittest.TestCase):
+ """
+ This tests parsing of arguments of the bind10 master process.
+ """
+ #TODO: Write tests for the original parsing, bad options, etc.
+ def test_no_opts(self):
+ """
+ Test correct default values when no options are passed.
+ """
+ options = parse_args([], TestOptParser)
+ self.assertEqual(None, options.data_path)
+ self.assertEqual(None, options.config_file)
+ self.assertEqual(None, options.cmdctl_port)
+
+ def test_data_path(self):
+ """
+ Test it can parse the data path.
+ """
+ self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
+ self.assertRaises(OptsError, parse_args, ['--data-path'],
+ TestOptParser)
+ options = parse_args(['-p', '/data/path'], TestOptParser)
+ self.assertEqual('/data/path', options.data_path)
+ options = parse_args(['--data-path=/data/path'], TestOptParser)
+ self.assertEqual('/data/path', options.data_path)
+
+ def test_config_filename(self):
+ """
+ Test it can parse the config switch.
+ """
+ self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
+ self.assertRaises(OptsError, parse_args, ['--config-file'],
+ TestOptParser)
+ options = parse_args(['-c', 'config-file'], TestOptParser)
+ self.assertEqual('config-file', options.config_file)
+ options = parse_args(['--config-file=config-file'], TestOptParser)
+ self.assertEqual('config-file', options.config_file)
+
+ def test_cmdctl_port(self):
+ """
+ Test it can parse the command control port.
+ """
+ self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
+ TestOptParser)
+ self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
+ TestOptParser)
+ self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
+ TestOptParser)
+ options = parse_args(['--cmdctl-port=1234'], TestOptParser)
+ self.assertEqual(1234, options.cmdctl_port)
+
+ def test_brittle(self):
+ """
+ Test we can use the "brittle" flag.
+ """
+ options = parse_args([], TestOptParser)
+ self.assertFalse(options.brittle)
+ options = parse_args(['--brittle'], TestOptParser)
+ self.assertTrue(options.brittle)
+
class TestPIDFile(unittest.TestCase):
def setUp(self):
self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
@@ -459,5 +654,34 @@ class TestPIDFile(unittest.TestCase):
self.assertRaises(IOError, dump_pid,
'nonexistent_dir' + os.sep + 'bind10.pid')
+class TestBrittle(unittest.TestCase):
+ def test_brittle_disabled(self):
+ bob = MockBob()
+ bob.start_all_processes()
+ bob.runnable = True
+
+ bob.reap_children()
+ self.assertTrue(bob.runnable)
+
+ def simulated_exit(self):
+ ret_val = self.exit_info
+ self.exit_info = (0, 0)
+ return ret_val
+
+ def test_brittle_enabled(self):
+ bob = MockBob()
+ bob.start_all_processes()
+ bob.runnable = True
+
+ bob.brittle = True
+ self.exit_info = (5, 0)
+ bob._get_process_exit_status = self.simulated_exit
+
+ old_stdout = sys.stdout
+ sys.stdout = open("/dev/null", "w")
+ bob.reap_children()
+ sys.stdout = old_stdout
+ self.assertFalse(bob.runnable)
+
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/bindctl/bindcmd.py b/src/bin/bindctl/bindcmd.py
index 83dab25..8973aa5 100644
--- a/src/bin/bindctl/bindcmd.py
+++ b/src/bin/bindctl/bindcmd.py
@@ -123,14 +123,19 @@ class BindCmdInterpreter(Cmd):
'''Parse commands from user and send them to cmdctl. '''
try:
if not self.login_to_cmdctl():
- return
+ return
self.cmdloop()
+ print('\nExit from bindctl')
except FailToLogin as err:
# error already printed when this was raised, ignoring
pass
except KeyboardInterrupt:
print('\nExit from bindctl')
+ except socket.error as err:
+ print('Failed to send request, the connection is closed')
+ except http.client.CannotSendRequest:
+ print('Can not send request, the connection is busy')
def _get_saved_user_info(self, dir, file_name):
''' Read all the available username and password pairs saved in
@@ -192,8 +197,10 @@ class BindCmdInterpreter(Cmd):
raise FailToLogin()
if response.status == http.client.OK:
- print(data + ' login as ' + row[0] )
- return True
+ # Is interactive?
+ if sys.stdin.isatty():
+ print(data + ' login as ' + row[0])
+ return True
count = 0
print("[TEMP MESSAGE]: username :root password :bind10")
@@ -273,8 +280,9 @@ class BindCmdInterpreter(Cmd):
self._update_commands()
def precmd(self, line):
- self._update_all_modules_info()
- return line
+ if line != 'EOF':
+ self._update_all_modules_info()
+ return line
def postcmd(self, stop, line):
'''Update the prompt after every command, but only if we
diff --git a/src/bin/bindctl/bindctl.1 b/src/bin/bindctl/bindctl.1
index e86eca2..97700d6 100644
--- a/src/bin/bindctl/bindctl.1
+++ b/src/bin/bindctl/bindctl.1
@@ -22,7 +22,7 @@
bindctl \- control and configure BIND 10
.SH "SYNOPSIS"
.HP \w'\fBbindctl\fR\ 'u
-\fBbindctl\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-h\fR] [\fB\-c\ \fR\fB\fIfile\fR\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-help\fR] [\fB\-\-certificate\-chain\ \fR\fB\fIfile\fR\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\fR] [\fB\-\-version\fR]
+\fBbindctl\fR [\fB\-a\ \fR\fB\fIaddress\fR\fR] [\fB\-h\fR] [\fB\-c\ \fR\fB\fIfile\fR\fR] [\fB\-p\ \fR\fB\fInumber\fR\fR] [\fB\-\-address\ \fR\fB\fIaddress\fR\fR] [\fB\-\-help\fR] [\fB\-\-certificate\-chain\ \fR\fB\fIfile\fR\fR] [\fB\-\-csv\-file\-dir\fR\fB\fIfile\fR\fR] [\fB\-\-port\ \fR\fB\fInumber\fR\fR] [\fB\-\-version\fR]
.SH "DESCRIPTION"
.PP
The
@@ -52,6 +52,11 @@ daemon\&. The default is 127\&.0\&.0\&.1\&.
The PEM formatted server certificate validation chain file\&.
.RE
.PP
+\fB\-\-csv\-file\-dir\fR\fIfile\fR
+.RS 4
+The directory name in which the user/password CSV file is stored (see AUTHENTICATION)\&. By default this option doesn\'t have any value, in which case the "\&.bind10" directory under the user\'s home directory will be used\&.
+.RE
+.PP
\fB\-h\fR, \fB\-\-help\fR
.RS 4
Display command usage\&.
@@ -85,10 +90,10 @@ Display the version number and exit\&.
.RE
.SH "AUTHENTICATION"
.PP
-The tool will authenticate using a username and password\&. On the first successful login, it will save the details to
-~/\&.bind10/default_user\&.csv
-which will be used for later uses of
-\fBbindctl\fR\&.
+The tool will authenticate using a username and password\&. On the first successful login, it will save the details to a comma\-separated\-value (CSV) file which will be used for later uses of
+\fBbindctl\fR\&. The file name is
+default_user\&.csv
+located under the directory specified by the \-\-csv\-file\-dir option\&.
.SH "USAGE"
.PP
The
diff --git a/src/bin/bindctl/tests/bindctl_test.py b/src/bin/bindctl/tests/bindctl_test.py
index dd492c1..0635b32 100644
--- a/src/bin/bindctl/tests/bindctl_test.py
+++ b/src/bin/bindctl/tests/bindctl_test.py
@@ -17,11 +17,16 @@
import unittest
import isc.cc.data
import os
+import io
+import sys
+import socket
+import http.client
import pwd
import getpass
from optparse import OptionParser
from isc.config.config_data import ConfigData, MultiConfigData
from isc.config.module_spec import ModuleSpec
+from isc.testutils.parse_args import TestOptParser, OptsError
from bindctl_main import set_bindctl_options
from bindctl import cmdparse
from bindctl import bindcmd
@@ -275,7 +280,33 @@ class FakeCCSession(MultiConfigData):
]
}
self.set_specification(ModuleSpec(spec))
-
+
+
+# fake socket
+class FakeSocket():
+ def __init__(self):
+ self.run = True
+
+ def connect(self, to):
+ if not self.run:
+ raise socket.error
+
+ def close(self):
+ self.run = False
+
+ def send(self, data):
+ if not self.run:
+ raise socket.error
+ return len(data)
+
+ def makefile(self, type):
+ return self
+
+ def sendall(self, data):
+ if not self.run:
+ raise socket.error
+ return len(data)
+
class TestConfigCommands(unittest.TestCase):
def setUp(self):
@@ -283,7 +314,47 @@ class TestConfigCommands(unittest.TestCase):
mod_info = ModuleInfo(name = "foo")
self.tool.add_module_info(mod_info)
self.tool.config_data = FakeCCSession()
-
+ self.stdout_backup = sys.stdout
+
+ def test_precmd(self):
+ def update_all_modules_info():
+ raise socket.error
+ def precmd(line):
+ self.tool.precmd(line)
+ self.tool._update_all_modules_info = update_all_modules_info
+ # If line is equals to 'EOF', _update_all_modules_info() shouldn't be called
+ precmd('EOF')
+ self.assertRaises(socket.error, precmd, 'continue')
+
+ def test_run(self):
+ def login_to_cmdctl():
+ return True
+ def cmd_loop():
+ self.tool._send_message("/module_spec", None)
+
+ self.tool.login_to_cmdctl = login_to_cmdctl
+ # rewrite cmdloop() to avoid interactive mode
+ self.tool.cmdloop = cmd_loop
+
+ self.tool.conn.sock = FakeSocket()
+ self.tool.conn.sock.close()
+
+ # validate log message for socket.err
+ socket_err_output = io.StringIO()
+ sys.stdout = socket_err_output
+ self.assertRaises(None, self.tool.run())
+ self.assertEqual("Failed to send request, the connection is closed\n",
+ socket_err_output.getvalue())
+ socket_err_output.close()
+
+ # validate log message for http.client.CannotSendRequest
+ cannot_send_output = io.StringIO()
+ sys.stdout = cannot_send_output
+ self.assertRaises(None, self.tool.run())
+ self.assertEqual("Can not send request, the connection is busy\n",
+ cannot_send_output.getvalue())
+ cannot_send_output.close()
+
def test_apply_cfg_command_int(self):
self.tool.location = '/'
@@ -332,10 +403,17 @@ class TestConfigCommands(unittest.TestCase):
# this should raise a TypeError
cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=\"a\"")
self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
-
+
cmd = cmdparse.BindCmdParse("config set identifier=\"foo/a_list\" value=[1]")
self.assertRaises(isc.cc.data.DataTypeError, self.tool.apply_config_cmd, cmd)
+ def tearDown(self):
+ sys.stdout = self.stdout_backup
+
+class FakeBindCmdInterpreter(bindcmd.BindCmdInterpreter):
+ def __init__(self):
+ pass
+
class TestBindCmdInterpreter(unittest.TestCase):
def _create_invalid_csv_file(self, csvfilename):
@@ -360,35 +438,23 @@ class TestBindCmdInterpreter(unittest.TestCase):
self.assertEqual(new_csv_dir, custom_cmd.csv_file_dir)
def test_get_saved_user_info(self):
+ old_stdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
cmd = bindcmd.BindCmdInterpreter()
users = cmd._get_saved_user_info('/notexist', 'csv_file.csv')
self.assertEqual([], users)
-
+
csvfilename = 'csv_file.csv'
self._create_invalid_csv_file(csvfilename)
users = cmd._get_saved_user_info('./', csvfilename)
self.assertEqual([], users)
os.remove(csvfilename)
+ sys.stdout = old_stdout
class TestCommandLineOptions(unittest.TestCase):
- class FakeParserError(Exception):
- """An exception thrown from FakeOptionParser on parser error.
- """
- pass
-
- class FakeOptionParser(OptionParser):
- """This fake class emulates the OptionParser class with customized
- error handling for the convenient of tests.
- """
- def __init__(self):
- OptionParser.__init__(self)
-
- def error(self, msg):
- raise TestCommandLineOptions.FakeParserError
-
def setUp(self):
- self.parser = self.FakeOptionParser()
+ self.parser = TestOptParser()
set_bindctl_options(self.parser)
def test_csv_file_dir(self):
@@ -401,7 +467,7 @@ class TestCommandLineOptions(unittest.TestCase):
self.assertEqual('some_dir', options.csv_file_dir)
# missing option arg; should trigger parser error.
- self.assertRaises(self.FakeParserError, self.parser.parse_args,
+ self.assertRaises(OptsError, self.parser.parse_args,
['--csv-file-dir'])
if __name__== "__main__":
diff --git a/src/bin/cfgmgr/b10-cfgmgr.8 b/src/bin/cfgmgr/b10-cfgmgr.8
index 03371af..719f4c6 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.8
+++ b/src/bin/cfgmgr/b10-cfgmgr.8
@@ -20,6 +20,9 @@
.\" -----------------------------------------------------------------
.SH "NAME"
b10-cfgmgr \- Configuration manager
+.SH "SYNOPSIS"
+.HP \w'\fBb10\-cfgmgr\fR\ 'u
+\fBb10\-cfgmgr\fR [\fB\-c\fR\fB\fIconfig\-filename\fR\fR] [\fB\-p\fR\fB\fIdata_path\fR\fR]
.SH "DESCRIPTION"
.PP
The
@@ -43,8 +46,21 @@ The daemon may be cleanly stopped by sending the SIGTERM signal to the process\&
When it exits, it saves its current configuration to
/usr/local/var/bind10\-devel/b10\-config\&.db\&.
+.SH "ARGUMENTS"
.PP
-The daemon has no command line options\&. It ignores any arguments\&.
+The arguments are as follows:
+.PP
+\fB\-c\fR\fIconfig\-filename\fR, \fB\-\-config\-filename\fR \fIconfig\-filename\fR
+.RS 4
+The configuration database filename to use\&. Can be either absolute or relative to data path\&.
+.sp
+Defaults to b10\-config\&.db
+.RE
+.PP
+\fB\-p\fR\fIdata\-path\fR, \fB\-\-data\-path\fR \fIdata\-path\fR
+.RS 4
+The path where BIND 10 looks for files\&. The configuration file is looked for here, if it is relative\&. If it is absolute, the path is ignored\&.
+.RE
.SH "FILES"
.PP
/usr/local/var/bind10\-devel/b10\-config\&.db
diff --git a/src/bin/cfgmgr/b10-cfgmgr.py.in b/src/bin/cfgmgr/b10-cfgmgr.py.in
index e37ec48..5355582 100755
--- a/src/bin/cfgmgr/b10-cfgmgr.py.in
+++ b/src/bin/cfgmgr/b10-cfgmgr.py.in
@@ -22,6 +22,7 @@ from isc.cc import SessionError
import isc.util.process
import signal
import os
+from optparse import OptionParser
isc.util.process.rename()
@@ -41,18 +42,34 @@ if "B10_FROM_SOURCE" in os.environ:
else:
PREFIX = "@prefix@"
DATA_PATH = "@localstatedir@/@PACKAGE@".replace("${prefix}", PREFIX)
+DEFAULT_CONFIG_FILE = "b10-config.db"
cm = None
+def parse_options(args=sys.argv[1:], Parser=OptionParser):
+ parser = Parser()
+ parser.add_option("-p", "--data-path", dest="data_path",
+ help="Directory to search for configuration files " +
+ "(default=" + DATA_PATH + ")", default=DATA_PATH)
+ parser.add_option("-c", "--config-filename", dest="config_file",
+ help="Configuration database filename " +
+ "(default=" + DEFAULT_CONFIG_FILE + ")",
+ default=DEFAULT_CONFIG_FILE)
+ (options, args) = parser.parse_args(args)
+ if args:
+ parser.error("No non-option arguments allowed")
+ return options
+
def signal_handler(signal, frame):
global cm
if cm:
cm.running = False
def main():
+ options = parse_options()
global cm
try:
- cm = ConfigManager(DATA_PATH)
+ cm = ConfigManager(options.data_path, options.config_file)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
cm.read_config()
diff --git a/src/bin/cfgmgr/b10-cfgmgr.xml b/src/bin/cfgmgr/b10-cfgmgr.xml
index 9505eee..785a058 100644
--- a/src/bin/cfgmgr/b10-cfgmgr.xml
+++ b/src/bin/cfgmgr/b10-cfgmgr.xml
@@ -41,16 +41,13 @@
</copyright>
</docinfo>
-<!--
<refsynopsisdiv>
<cmdsynopsis>
- <command></command>
- <arg><option></option></arg>
- <arg choice="opt"></arg>
- <arg choice="opt"></arg>
+ <command>b10-cfgmgr</command>
+ <arg><option>-c<replaceable>config-filename</replaceable></option></arg>
+ <arg><option>-p<replaceable>data_path</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
--->
<refsect1>
<title>DESCRIPTION</title>
@@ -87,30 +84,41 @@
<!-- TODO: does it periodically save configuration? -->
</para>
- <para>
- The daemon has no command line options. It ignores any arguments.
<!-- TODO: add a verbose or quiet switch so it is not so noisy -->
- </para>
</refsect1>
-<!--
<refsect1>
<title>ARGUMENTS</title>
- <para>
- <orderedlist numeration="loweralpha">
+
+ <para>The arguments are as follows:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term>
+ <option>-c</option><replaceable>config-filename</replaceable>,
+ <option>--config-filename</option> <replaceable>config-filename</replaceable>
+ </term>
<listitem>
- <para>
- </para>
+ <para>The configuration database filename to use. Can be either
+ absolute or relative to data path.</para>
+ <para>Defaults to b10-config.db</para>
</listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>
+ <option>-p</option><replaceable>data-path</replaceable>,
+ <option>--data-path</option> <replaceable>data-path</replaceable>
+ </term>
<listitem>
- <para>
- </para>
+ <para>The path where BIND 10 looks for files. The
+ configuration file is looked for here, if it is relative. If it is
+ absolute, the path is ignored.</para>
</listitem>
- </orderedlist>
- </para>
-
+ </varlistentry>
+ </variablelist>
</refsect1>
--->
+
<refsect1>
<title>FILES</title>
<!-- TODO: fix path -->
diff --git a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
index 8847b18..037a106 100644
--- a/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
+++ b/src/bin/cfgmgr/tests/b10-cfgmgr_test.py.in
@@ -20,9 +20,10 @@
import unittest
import os
import sys
+from isc.testutils.parse_args import OptsError, TestOptParser
class MyConfigManager:
- def __init__(self, path):
+ def __init__(self, path, filename):
self._path = path
self.read_config_called = False
self.notify_boss_called = False
@@ -88,6 +89,69 @@ class TestConfigManagerStartup(unittest.TestCase):
sys.modules.pop("b10-cfgmgr")
+class TestParseArgs(unittest.TestCase):
+ """
+ Test for the parsing of command line arguments. We provide a different
+ array to parse instead.
+ """
+
+ def test_defaults(self):
+ """
+ Test the default values when no options are provided.
+ """
+ # Pass it empty array, not our arguments
+ b = __import__("b10-cfgmgr")
+ parsed = b.parse_options([], TestOptParser)
+ self.assertEqual(b.DATA_PATH, parsed.data_path)
+ self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+
+ def test_wrong_args(self):
+ """
+ Test it fails when we pass invalid option.
+ """
+ b = __import__("b10-cfgmgr")
+ self.assertRaises(OptsError, b.parse_options, ['--wrong-option'],
+ TestOptParser)
+
+ def test_not_arg(self):
+ """
+ Test it fails when there's an argument that's not option
+ (eg. without -- at the beginning).
+ """
+ b = __import__("b10-cfgmgr")
+ self.assertRaises(OptsError, b.parse_options, ['not-option'],
+ TestOptParser)
+
+ def test_datapath(self):
+ """
+ Test overwriting the data path.
+ """
+ b = __import__("b10-cfgmgr")
+ parsed = b.parse_options(['--data-path=/path'], TestOptParser)
+ self.assertEqual('/path', parsed.data_path)
+ self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+ parsed = b.parse_options(['-p', '/path'], TestOptParser)
+ self.assertEqual('/path', parsed.data_path)
+ self.assertEqual(b.DEFAULT_CONFIG_FILE, parsed.config_file)
+ self.assertRaises(OptsError, b.parse_options, ['-p'], TestOptParser)
+ self.assertRaises(OptsError, b.parse_options, ['--data-path'],
+ TestOptParser)
+
+ def test_db_filename(self):
+ """
+ Test setting the configuration database file.
+ """
+ b = __import__("b10-cfgmgr")
+ parsed = b.parse_options(['--config-filename=filename'],
+ TestOptParser)
+ self.assertEqual(b.DATA_PATH, parsed.data_path)
+ self.assertEqual("filename", parsed.config_file)
+ parsed = b.parse_options(['-c', 'filename'], TestOptParser)
+ self.assertEqual(b.DATA_PATH, parsed.data_path)
+ self.assertEqual("filename", parsed.config_file)
+ self.assertRaises(OptsError, b.parse_options, ['-c'], TestOptParser)
+ self.assertRaises(OptsError, b.parse_options, ['--config-filename'],
+ TestOptParser)
if __name__ == '__main__':
unittest.main()
diff --git a/src/bin/cmdctl/cmdctl.py.in b/src/bin/cmdctl/cmdctl.py.in
index b996596..f1c1021 100755
--- a/src/bin/cmdctl/cmdctl.py.in
+++ b/src/bin/cmdctl/cmdctl.py.in
@@ -1,7 +1,6 @@
#!@PYTHON@
# Copyright (C) 2010 Internet Systems Consortium.
-# Copyright (C) 2010 CZ NIC
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/bin/msgq/tests/msgq_test.py b/src/bin/msgq/tests/msgq_test.py
index 59fcf41..f926845 100644
--- a/src/bin/msgq/tests/msgq_test.py
+++ b/src/bin/msgq/tests/msgq_test.py
@@ -117,7 +117,7 @@ class SendNonblock(unittest.TestCase):
Tests that the whole thing will not get blocked if someone does not read.
"""
- def terminate_check(self, task, timeout = 10):
+ def terminate_check(self, task, timeout=30):
"""
Runs task in separate process (task is a function) and checks
it terminates sooner than timeout.
@@ -194,7 +194,7 @@ class SendNonblock(unittest.TestCase):
length = len(data)
queue_pid = os.fork()
if queue_pid == 0:
- signal.alarm(30)
+ signal.alarm(120)
msgq.setup_poller()
msgq.register_socket(queue)
msgq.run()
diff --git a/src/bin/resolver/Makefile.am b/src/bin/resolver/Makefile.am
index 36b8551..54e15bd 100644
--- a/src/bin/resolver/Makefile.am
+++ b/src/bin/resolver/Makefile.am
@@ -51,6 +51,7 @@ b10_resolver_LDADD += $(top_builddir)/src/lib/log/liblog.la
b10_resolver_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
b10_resolver_LDADD += $(top_builddir)/src/lib/cache/libcache.la
b10_resolver_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+b10_resolver_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
b10_resolver_LDADD += $(top_builddir)/src/bin/auth/change_user.o
b10_resolver_LDFLAGS = -pthread
diff --git a/src/bin/resolver/main.cc b/src/bin/resolver/main.cc
index d987c74..092ec54 100644
--- a/src/bin/resolver/main.cc
+++ b/src/bin/resolver/main.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 <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
@@ -30,6 +32,7 @@
#include <exceptions/exceptions.h>
#include <dns/buffer.h>
+#include <dns/rcode.h>
#include <dns/message.h>
#include <dns/messagerenderer.h>
@@ -45,6 +48,9 @@
#include <resolver/spec_config.h>
#include <resolver/resolver.h>
+#include <cache/resolver_cache.h>
+#include <nsas/nameserver_address_store.h>
+
#include <log/dummylog.h>
using namespace std;
@@ -59,7 +65,7 @@ namespace {
static const string PROGRAM = "Resolver";
IOService io_service;
-static Resolver *resolver;
+static boost::shared_ptr<Resolver> resolver;
ConstElementPtr
my_config_handler(ConstElementPtr new_config) {
@@ -135,15 +141,59 @@ main(int argc, char* argv[]) {
specfile = string(RESOLVER_SPECFILE_LOCATION);
}
- resolver = new Resolver();
+ resolver = boost::shared_ptr<Resolver>(new Resolver());
dlog("Server created.");
SimpleCallback* checkin = resolver->getCheckinProvider();
DNSLookup* lookup = resolver->getDNSLookupProvider();
DNSAnswer* answer = resolver->getDNSAnswerProvider();
+ isc::nsas::NameserverAddressStore nsas(resolver);
+ resolver->setNameserverAddressStore(nsas);
+
+ isc::cache::ResolverCache cache;
+ resolver->setCache(cache);
+
+ // TODO priming query, remove root from direct
+ // Fake a priming query result here (TODO2 how to flag non-expiry?)
+ // propagation to runningquery. And check for forwarder mode?
+ isc::dns::QuestionPtr root_question(new isc::dns::Question(
+ isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS()));
+ isc::dns::RRsetPtr root_ns_rrset(new isc::dns::RRset(isc::dns::Name("."),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::NS(),
+ isc::dns::RRTTL(8888)));
+ root_ns_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::NS(),
+ isc::dns::RRClass::IN(),
+ "l.root-servers.net."));
+ isc::dns::RRsetPtr root_a_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::A(),
+ isc::dns::RRTTL(8888)));
+ root_a_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::A(),
+ isc::dns::RRClass::IN(),
+ "199.7.83.42"));
+ isc::dns::RRsetPtr root_aaaa_rrset(new isc::dns::RRset(isc::dns::Name("l.root-servers.net"),
+ isc::dns::RRClass::IN(),
+ isc::dns::RRType::AAAA(),
+ isc::dns::RRTTL(8888)));
+ root_aaaa_rrset->addRdata(isc::dns::rdata::createRdata(isc::dns::RRType::AAAA(),
+ isc::dns::RRClass::IN(),
+ "2001:500:3::42"));
+ isc::dns::MessagePtr priming_result(new isc::dns::Message(isc::dns::Message::RENDER));
+ priming_result->setRcode(isc::dns::Rcode::NOERROR());
+ priming_result->addQuestion(root_question);
+ priming_result->addRRset(isc::dns::Message::SECTION_ANSWER, root_ns_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_a_rrset);
+ priming_result->addRRset(isc::dns::Message::SECTION_ADDITIONAL, root_aaaa_rrset);
+ cache.update(*priming_result);
+ cache.update(root_ns_rrset);
+ cache.update(root_a_rrset);
+ cache.update(root_aaaa_rrset);
+
DNSService dns_service(io_service, checkin, lookup, answer);
-
resolver->setDNSService(dns_service);
dlog("IOService created.");
@@ -172,7 +222,6 @@ main(int argc, char* argv[]) {
delete config_session;
delete cc_session;
- delete resolver;
return (ret);
}
diff --git a/src/bin/resolver/resolver.cc b/src/bin/resolver/resolver.cc
index aa80d60..7d570dd 100644
--- a/src/bin/resolver/resolver.cc
+++ b/src/bin/resolver/resolver.cc
@@ -41,6 +41,8 @@
#include <dns/messagerenderer.h>
#include <server_common/portconfig.h>
+#include <resolve/recursive_query.h>
+
#include <log/dummylog.h>
#include <resolver/resolver.h>
@@ -74,10 +76,15 @@ public:
queryShutdown();
}
- void querySetup(DNSService& dnss) {
+ void querySetup(DNSService& dnss,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache)
+ {
assert(!rec_query_); // queryShutdown must be called first
dlog("Query setup");
- rec_query_ = new RecursiveQuery(dnss, upstream_,
+ rec_query_ = new RecursiveQuery(dnss,
+ nsas, cache,
+ upstream_,
upstream_root_,
query_timeout_,
client_timeout_,
@@ -129,7 +136,7 @@ public:
}
}
}
-
+
void resolve(const isc::dns::QuestionPtr& question,
const isc::resolve::ResolverInterface::CallbackPtr& callback);
@@ -326,7 +333,8 @@ Resolver::Resolver() :
impl_(new ResolverImpl()),
checkin_(new ConfigCheck(this)),
dns_lookup_(new MessageLookup(this)),
- dns_answer_(new MessageAnswer)
+ dns_answer_(new MessageAnswer),
+ configured_(false)
{}
Resolver::~Resolver() {
@@ -342,6 +350,19 @@ Resolver::setDNSService(asiolink::DNSService& dnss) {
}
void
+Resolver::setNameserverAddressStore(isc::nsas::NameserverAddressStore& nsas)
+{
+ nsas_ = &nsas;
+}
+
+void
+Resolver::setCache(isc::cache::ResolverCache& cache)
+{
+ cache_ = &cache;
+}
+
+
+void
Resolver::setConfigSession(ModuleCCSession* config_session) {
impl_->config_session_ = config_session;
}
@@ -436,6 +457,9 @@ Resolver::processMessage(const IOMessage& io_message,
} else if (qtype == RRType::IXFR()) {
makeErrorMessage(query_message, answer_message,
buffer, Rcode::NOTIMP());
+ } else if (question->getClass() != RRClass::IN()) {
+ makeErrorMessage(query_message, answer_message,
+ buffer, Rcode::REFUSED());
} else {
// The RecursiveQuery object will post the "resume" event to the
// DNSServer when an answer arrives, so we don't have to do it now.
@@ -534,6 +558,15 @@ Resolver::updateConfig(ConstElementPtr config) {
if (listenAddressesE) {
setListenAddresses(listenAddresses);
need_query_restart = true;
+ } else {
+ if (!configured_) {
+ // TODO: ModuleSpec needs getDefault()
+ AddressList initial_addresses;
+ initial_addresses.push_back(AddressPair("127.0.0.1", 53));
+ initial_addresses.push_back(AddressPair("::1", 53));
+ setListenAddresses(initial_addresses);
+ need_query_restart = true;
+ }
}
if (forwardAddressesE) {
setForwardAddresses(forwardAddresses);
@@ -550,8 +583,9 @@ Resolver::updateConfig(ConstElementPtr config) {
if (need_query_restart) {
impl_->queryShutdown();
- impl_->querySetup(*dnss_);
+ impl_->querySetup(*dnss_, *nsas_, *cache_);
}
+ setConfigured();
return (isc::config::createAnswer());
} catch (const isc::Exception& error) {
dlog(string("error in config: ") + error.what(),true);
diff --git a/src/bin/resolver/resolver.h b/src/bin/resolver/resolver.h
index 2ae8079..8f07ff7 100644
--- a/src/bin/resolver/resolver.h
+++ b/src/bin/resolver/resolver.h
@@ -21,8 +21,19 @@
#include <cc/data.h>
#include <config/ccsession.h>
+#include <dns/message.h>
+#include <dns/buffer.h>
-#include <asiolink/asiolink.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_service.h>
+#include <asiolink/dns_server.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/simple_callback.h>
+
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
#include <resolve/resolver_interface.h>
@@ -86,10 +97,26 @@ public:
/// \brief Assign an ASIO IO Service queue to this Resolver object
void setDNSService(asiolink::DNSService& dnss);
+
+ /// \brief Assign a NameserverAddressStore to this Resolver object
+ void setNameserverAddressStore(isc::nsas::NameserverAddressStore &nsas);
+
+ /// \brief Assign a cache to this Resolver object
+ void setCache(isc::cache::ResolverCache& cache);
/// \brief Return this object's ASIO IO Service queue
asiolink::DNSService& getDNSService() const { return (*dnss_); }
+ /// \brief Returns this object's NSAS
+ isc::nsas::NameserverAddressStore& getNameserverAddressStore() const {
+ return *nsas_;
+ };
+
+ /// \brief Returns this object's ResolverCache
+ isc::cache::ResolverCache& getResolverCache() const {
+ return *cache_;
+ };
+
/// \brief Return pointer to the DNS Lookup callback function
asiolink::DNSLookup* getDNSLookupProvider() { return (dns_lookup_); }
@@ -100,6 +127,13 @@ public:
asiolink::SimpleCallback* getCheckinProvider() { return (checkin_); }
/**
+ * \brief Tell the Resolver that is has already been configured
+ * so that it will only set some defaults the first time
+ * (used by updateConfig() and tests)
+ */
+ void setConfigured() { configured_ = true; };
+
+ /**
* \brief Specify the list of upstream servers.
*
* Specify the list off addresses of upstream servers to forward queries
@@ -208,6 +242,12 @@ private:
asiolink::SimpleCallback* checkin_;
asiolink::DNSLookup* dns_lookup_;
asiolink::DNSAnswer* dns_answer_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache* cache_;
+ // This value is initally false, and will be set to true
+ // when the initial configuration is done (updateConfig
+ // should act a tiny bit different on the very first call)
+ bool configured_;
};
#endif // __RESOLVER_H
diff --git a/src/bin/resolver/response_scrubber.h b/src/bin/resolver/response_scrubber.h
index c3fce57..680aa5a 100644
--- a/src/bin/resolver/response_scrubber.h
+++ b/src/bin/resolver/response_scrubber.h
@@ -177,7 +177,7 @@
/// Qu: www.sub.example.com\n
/// Zo: example.com
///
-/// An: <nothing>
+/// An: (nothing)
///
/// Au(1): sub.example.com NS ns0.sub.example.com\n
/// Au(2): sub.example.com NS ns1.example.net
@@ -312,7 +312,7 @@ public:
/// QNAME is equal to or in the supplied relationship with the given name.
///
/// \param section Section of the message to be scrubbed.
- /// \param zone Names against which RRsets should be checked. Note that
+ /// \param names Names against which RRsets should be checked. Note that
/// this is a vector of pointers to Name objects; they are assumed to
/// independently exist, and the caller retains ownership of them and is
/// assumed to destroy them when needed.
diff --git a/src/bin/resolver/tests/Makefile.am b/src/bin/resolver/tests/Makefile.am
index eb7e3e1..b85c223 100644
--- a/src/bin/resolver/tests/Makefile.am
+++ b/src/bin/resolver/tests/Makefile.am
@@ -40,6 +40,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
# Note the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS
diff --git a/src/bin/resolver/tests/resolver_config_unittest.cc b/src/bin/resolver/tests/resolver_config_unittest.cc
index 1d6415b..c1ff853 100644
--- a/src/bin/resolver/tests/resolver_config_unittest.cc
+++ b/src/bin/resolver/tests/resolver_config_unittest.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 <string>
#include <gtest/gtest.h>
@@ -42,6 +44,7 @@ class ResolverConfig : public ::testing::Test {
dnss(ios, NULL, NULL, NULL)
{
server.setDNSService(dnss);
+ server.setConfigured();
}
void invalidTest(const string &JSON, const string& name);
};
diff --git a/src/bin/sockcreator/Makefile.am b/src/bin/sockcreator/Makefile.am
new file mode 100644
index 0000000..1ac4640
--- /dev/null
+++ b/src/bin/sockcreator/Makefile.am
@@ -0,0 +1,18 @@
+SUBDIRS = tests
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+
+CLEANFILES = *.gcno *.gcda
+
+pkglibexec_PROGRAMS = b10-sockcreator
+
+b10_sockcreator_SOURCES = sockcreator.cc sockcreator.h main.cc
+b10_sockcreator_LDADD = $(top_builddir)/src/lib/util/io/libutil_io.la
diff --git a/src/bin/sockcreator/README b/src/bin/sockcreator/README
new file mode 100644
index 0000000..4dbbee7
--- /dev/null
+++ b/src/bin/sockcreator/README
@@ -0,0 +1,49 @@
+The socket creator
+==================
+
+The only thing we need higher rights than standard user is binding sockets to
+ports lower than 1024. So we will have a separate process that keeps the
+rights, while the rests drop them for security reasons.
+
+This process is the socket creator. Its goal is to be as simple as possible
+and to contain as little code as possible to minimise the amount of code
+running with higher privileges (to minimize the number of bugs and make
+checking/auditing it easier). It uses low-level OS API instead of some
+fancy library for that reason. It has only fixed-length reads so there's no
+place for buffer overruns.
+
+Protocol
+--------
+
+It talks with whoever started it by its stdin/stdout. It reads simple
+binary protocol from stdin and does what the commands ask. Command is a single
+byte (usually from the printable range, so it is easier to debug and guess
+what it does), followed by parameters.
+
+Note that as send_fd and recv_fd works only with unix domain socket, it's stdio
+must be a socket, not pipe.
+
+* 'T': It has no parameters. It asks the socket creator to terminate.
+
+* 'S' 'U|T' '4|6' port address: Asks it to create a port. First parameter
+ tels the socket type (either UDP or TCP). The second one is address family
+ (either IPv4 or IPv6). Then there's 2 bytes of the port number, in the
+ network byte order. The last one is either 4 or 16 bytes of address, as
+ they would be passed to bind (note that both parameters are already prepared,
+ like hton called on them).
+
+ The answer to this is either 'S' directly followed by the socket (using
+ sendmsg) if it is successful. If it fails, 'E' is returned instead, followed
+ by either 'S' or 'B' (either socket() or bind() call failed). Then there is
+ one int (architecture-dependent length and endianess), which is the errno
+ value after the failure.
+
+The creator may also send these messages at any time (but not in the middle
+of another message):
+
+* 'F': A fatal error has been detected. It is followed by one byte of error
+ condition code and then the creator terminates with non-zero status.
+
+ The conditions are:
+ * 'I': Invalid input (eg. someone sent a wrong letter and it does not
+ understand it).
diff --git a/src/bin/sockcreator/main.cc b/src/bin/sockcreator/main.cc
new file mode 100644
index 0000000..37da303
--- /dev/null
+++ b/src/bin/sockcreator/main.cc
@@ -0,0 +1,26 @@
+// Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include "sockcreator.h"
+
+using namespace isc::socket_creator;
+
+int
+main() {
+ /*
+ * TODO Maybe use some OS-specific caps interface and drop everything
+ * but ability to bind ports? It would be nice.
+ */
+ return run(0, 1); // Read commands from stdin, output to stdout
+}
diff --git a/src/bin/sockcreator/sockcreator.cc b/src/bin/sockcreator/sockcreator.cc
new file mode 100644
index 0000000..6b50813
--- /dev/null
+++ b/src/bin/sockcreator/sockcreator.cc
@@ -0,0 +1,151 @@
+// 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 "sockcreator.h"
+
+#include <util/io/fd.h>
+
+#include <unistd.h>
+#include <cerrno>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+using namespace isc::util::io;
+
+namespace isc {
+namespace socket_creator {
+
+int
+get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len)
+{
+ int sock(socket(bind_addr->sa_family, type, 0));
+ if (sock == -1) {
+ return -1;
+ }
+ if (bind(sock, bind_addr, addr_len) == -1) {
+ return -2;
+ }
+ return sock;
+}
+
+// These are macros so they can exit the function
+#define READ(WHERE, HOW_MANY) do { \
+ size_t how_many = (HOW_MANY); \
+ if (read_data(input_fd, (WHERE), how_many) < how_many) { \
+ return 1; \
+ } \
+ } while (0)
+
+#define WRITE(WHAT, HOW_MANY) do { \
+ if (!write_data(output_fd, (WHAT), (HOW_MANY))) { \
+ return 2; \
+ } \
+ } while (0)
+
+#define DEFAULT \
+ default: /* Unrecognized part of protocol */ \
+ WRITE("FI", 2); \
+ return 3;
+
+int
+run(const int input_fd, const int output_fd, const get_sock_t get_sock,
+ const send_fd_t send_fd)
+{
+ for (;;) {
+ // Read the command
+ char command;
+ READ(&command, 1);
+ switch (command) {
+ case 'T': // The "terminate" command
+ return 0;
+ case 'S': { // Create a socket
+ // Read what type of socket they want
+ char type[2];
+ READ(type, 2);
+ // Read the address they ask for
+ struct sockaddr *addr(NULL);
+ size_t addr_len(0);
+ struct sockaddr_in addr_in;
+ struct sockaddr_in6 addr_in6;
+ switch (type[1]) { // The address family
+ /*
+ * Here are some casts. They are required by C++ and
+ * the low-level interface (they are implicit in C).
+ */
+ case '4':
+ addr = static_cast<struct sockaddr *>(
+ static_cast<void *>(&addr_in));
+ addr_len = sizeof addr_in;
+ memset(&addr_in, 0, sizeof addr_in);
+ addr_in.sin_family = AF_INET;
+ READ(static_cast<char *>(static_cast<void *>(
+ &addr_in.sin_port)), 2);
+ READ(static_cast<char *>(static_cast<void *>(
+ &addr_in.sin_addr.s_addr)), 4);
+ break;
+ case '6':
+ addr = static_cast<struct sockaddr *>(
+ static_cast<void *>(&addr_in6));
+ addr_len = sizeof addr_in6;
+ memset(&addr_in6, 0, sizeof addr_in6);
+ addr_in6.sin6_family = AF_INET6;
+ READ(static_cast<char *>(static_cast<void *>(
+ &addr_in6.sin6_port)), 2);
+ READ(static_cast<char *>(static_cast<void *>(
+ &addr_in6.sin6_addr.s6_addr)), 16);
+ break;
+ DEFAULT
+ }
+ int sock_type;
+ switch (type[0]) { // Translate the type
+ case 'T':
+ sock_type = SOCK_STREAM;
+ break;
+ case 'U':
+ sock_type = SOCK_DGRAM;
+ break;
+ DEFAULT
+ }
+ int result(get_sock(sock_type, addr, addr_len));
+ if (result >= 0) { // We got the socket
+ WRITE("S", 1);
+ // FIXME: Check the output and write a test for it
+ send_fd(output_fd, result);
+ } else {
+ WRITE("E", 1);
+ switch (result) {
+ case -1:
+ WRITE("S", 1);
+ break;
+ case -2:
+ WRITE("B", 1);
+ break;
+ default:
+ return 4;
+ }
+ int error(errno);
+ WRITE(static_cast<char *>(static_cast<void *>(&error)),
+ sizeof error);
+ }
+ break;
+ }
+ DEFAULT
+ }
+ }
+}
+
+} // End of the namespaces
+}
diff --git a/src/bin/sockcreator/sockcreator.h b/src/bin/sockcreator/sockcreator.h
new file mode 100644
index 0000000..ddf9a09
--- /dev/null
+++ b/src/bin/sockcreator/sockcreator.h
@@ -0,0 +1,100 @@
+// 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.
+
+/**
+ * \file sockcreator.h
+ * \short Socket creator functionality.
+ *
+ * This module holds the functionality of the socket creator. It is
+ * a separate module from main to ease up the tests.
+ */
+
+#ifndef __SOCKCREATOR_H
+#define __SOCKCREATOR_H 1
+
+#include <util/io/fd_share.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+namespace isc {
+namespace socket_creator {
+
+/**
+ * \short Create a socket and bind it.
+ *
+ * This is just a bundle of socket() and bind() calls. The sa_family of
+ * bind_addr is used to determine the domain of the socket.
+ *
+ * \return The file descriptor of the newly created socket, if everything
+ * goes well. A negative number is returned if an error occurs -
+ * -1 if the socket() call fails or -2 if bind() fails. In case of error,
+ * errno is set (or better, left intact from socket() or bind()).
+ * \param type The type of socket to create (SOCK_STREAM, SOCK_DGRAM, etc).
+ * \param bind_addr The address to bind.
+ * \param addr_len The actual length of bind_addr.
+ */
+int
+get_sock(const int type, struct sockaddr *bind_addr, const socklen_t addr_len);
+
+/**
+ * Type of the get_sock function, to pass it as parameter.
+ */
+typedef
+int
+(*get_sock_t)(const int, struct sockaddr *, const socklen_t);
+
+/**
+ * Type of the send_fd() function, so it can be passed as a parameter.
+ */
+typedef
+int
+(*send_fd_t)(const int, const int);
+
+/**
+ * \short Infinite loop parsing commands and returning the sockets.
+ *
+ * This reads commands and socket descriptions from the input_fd
+ * file descriptor, creates sockets and writes the results (socket or
+ * error) to output_fd.
+ *
+ * Current errors are:
+ * - 1: Read error
+ * - 2: Write error
+ * - 3: Protocol error (unknown command, etc)
+ * - 4: Some internal inconsistency detected
+ *
+ * It terminates either if a command asks it to or when unrecoverable
+ * error happens.
+ *
+ * \return Like a return value of a main - 0 means everything OK, anything
+ * else is error.
+ * \param input_fd Here is where it reads the commads.
+ * \param output_fd Here is where it writes the results.
+ * \param get_sock_fun The function that is used to create the sockets.
+ * This should be left on the default value, the parameter is here
+ * for testing purposes.
+ * \param send_fd_fun The function that is used to send the socket over
+ * a file descriptor. This should be left on the default value, it is
+ * here for testing purposes.
+ */
+int
+run(const int input_fd, const int output_fd,
+ const get_sock_t get_sock_fun = get_sock,
+ const send_fd_t send_fd_fun = isc::util::io::send_fd);
+
+} // End of the namespaces
+}
+
+#endif // __SOCKCREATOR_H
diff --git a/src/bin/sockcreator/tests/Makefile.am b/src/bin/sockcreator/tests/Makefile.am
new file mode 100644
index 0000000..2e1307a
--- /dev/null
+++ b/src/bin/sockcreator/tests/Makefile.am
@@ -0,0 +1,25 @@
+CLEANFILES = *.gcno *.gcda
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = ../sockcreator.cc ../sockcreator.h
+run_unittests_SOURCES += sockcreator_tests.cc
+run_unittests_SOURCES += run_unittests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
+run_unittests_LDADD += \
+ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/bin/sockcreator/tests/run_unittests.cc b/src/bin/sockcreator/tests/run_unittests.cc
new file mode 100644
index 0000000..e787ab1
--- /dev/null
+++ b/src/bin/sockcreator/tests/run_unittests.cc
@@ -0,0 +1,22 @@
+// 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>
+
+int
+main(int argc, char *argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/src/bin/sockcreator/tests/sockcreator_tests.cc b/src/bin/sockcreator/tests/sockcreator_tests.cc
new file mode 100644
index 0000000..73cbf48
--- /dev/null
+++ b/src/bin/sockcreator/tests/sockcreator_tests.cc
@@ -0,0 +1,273 @@
+// 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 "../sockcreator.h"
+
+#include <util/unittests/fork.h>
+#include <util/io/fd.h>
+
+#include <gtest/gtest.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <cstring>
+#include <cerrno>
+
+using namespace isc::socket_creator;
+using namespace isc::util::unittests;
+using namespace isc::util::io;
+
+namespace {
+
+/*
+ * Generic version of the creation of socket test. It just tries to
+ * create the socket and checks the result is not negative (eg.
+ * it is valid descriptor) and that it can listen.
+ *
+ * This is a macro so ASSERT_* does abort the TEST, not just the
+ * function inside.
+ */
+#define TEST_ANY_CREATE(SOCK_TYPE, ADDR_TYPE, ADDR_FAMILY, FAMILY_FIELD, \
+ ADDR_SET, CHECK_SOCK) \
+ do { \
+ /*
+ * This should create an address that binds on all interfaces
+ * and lets the OS choose a free port.
+ */ \
+ struct ADDR_TYPE addr; \
+ memset(&addr, 0, sizeof addr); \
+ ADDR_SET(addr); \
+ addr.FAMILY_FIELD = ADDR_FAMILY; \
+ struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
+ static_cast<void *>(&addr)); \
+ \
+ int socket = get_sock(SOCK_TYPE, addr_ptr, sizeof addr); \
+ /* Provide even nice error message. */ \
+ ASSERT_GE(socket, 0) << "Couldn't create a socket of type " \
+ #SOCK_TYPE " and family " #ADDR_FAMILY ", failed with " \
+ << socket << " and error " << strerror(errno); \
+ CHECK_SOCK(ADDR_TYPE, socket); \
+ EXPECT_EQ(0, close(socket)); \
+ } while (0)
+
+// Just helper macros
+#define INADDR_SET(WHAT) do { WHAT.sin_addr.s_addr = INADDR_ANY; } while (0)
+#define IN6ADDR_SET(WHAT) do { WHAT.sin6_addr = in6addr_loopback; } while (0)
+// If the get_sock returned something useful, listen must work
+#define TCP_CHECK(UNUSED, SOCKET) do { \
+ EXPECT_EQ(0, listen(SOCKET, 1)); \
+ } while (0)
+// More complicated with UDP, so we send a packet to ourselfs and se if it
+// arrives
+#define UDP_CHECK(ADDR_TYPE, SOCKET) do { \
+ struct ADDR_TYPE addr; \
+ memset(&addr, 0, sizeof addr); \
+ struct sockaddr *addr_ptr = static_cast<struct sockaddr *>( \
+ static_cast<void *>(&addr)); \
+ \
+ socklen_t len = sizeof addr; \
+ ASSERT_EQ(0, getsockname(SOCKET, addr_ptr, &len)); \
+ ASSERT_EQ(5, sendto(SOCKET, "test", 5, 0, addr_ptr, sizeof addr)) << \
+ "Send failed with error " << strerror(errno) << " on socket " << \
+ SOCKET; \
+ char buffer[5]; \
+ ASSERT_EQ(5, recv(SOCKET, buffer, 5, 0)) << \
+ "Recv failed with error " << strerror(errno) << " on socket " << \
+ SOCKET; \
+ EXPECT_STREQ("test", buffer); \
+ } while (0)
+
+/*
+ * Several tests to ensure we can create the sockets.
+ */
+TEST(get_sock, udp4_create) {
+ TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
+ UDP_CHECK);
+}
+
+TEST(get_sock, tcp4_create) {
+ TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in, AF_INET, sin_family, INADDR_SET,
+ TCP_CHECK);
+}
+
+TEST(get_sock, udp6_create) {
+ TEST_ANY_CREATE(SOCK_DGRAM, sockaddr_in6, AF_INET6, sin6_family,
+ IN6ADDR_SET, UDP_CHECK);
+}
+
+TEST(get_sock, tcp6_create) {
+ TEST_ANY_CREATE(SOCK_STREAM, sockaddr_in6, AF_INET6, sin6_family,
+ IN6ADDR_SET, TCP_CHECK);
+}
+
+/*
+ * Try to ask the get_sock function some nonsense and test if it
+ * is able to report error.
+ */
+TEST(get_sock, fail_with_nonsense) {
+ struct sockaddr addr;
+ memset(&addr, 0, sizeof addr);
+ ASSERT_LT(get_sock(0, &addr, sizeof addr), 0);
+}
+
+/*
+ * Helper functions to pass to run during testing.
+ */
+int
+get_sock_dummy(const int type, struct sockaddr *addr, const socklen_t)
+{
+ int result(0);
+ int port(0);
+ /*
+ * We encode the type and address family into the int and return it.
+ * Lets ignore the port and address for now
+ * First bit is 1 if it is known type. Second tells if TCP or UDP.
+ * The familly is similar - third bit is known address family,
+ * the fourth is the family.
+ */
+ switch (type) {
+ case SOCK_STREAM:
+ result += 1;
+ break;
+ case SOCK_DGRAM:
+ result += 3;
+ break;
+ }
+ switch (addr->sa_family) {
+ case AF_INET:
+ result += 4;
+ port = static_cast<struct sockaddr_in *>(
+ static_cast<void *>(addr))->sin_port;
+ break;
+ case AF_INET6:
+ result += 12;
+ port = static_cast<struct sockaddr_in6 *>(
+ static_cast<void *>(addr))->sin6_port;
+ break;
+ }
+ /*
+ * The port should be 0xffff. If it's not, we change the result.
+ * The port of 0xbbbb means bind should fail and 0xcccc means
+ * socket should fail.
+ */
+ if (port != 0xffff) {
+ errno = 0;
+ if (port == 0xbbbb) {
+ return -2;
+ } else if (port == 0xcccc) {
+ return -1;
+ } else {
+ result += 16;
+ }
+ }
+ return result;
+}
+
+int
+send_fd_dummy(const int destination, const int what)
+{
+ /*
+ * Make sure it is 1 byte so we know the length. We do not use more during
+ * the test anyway.
+ */
+ char fd_data(what);
+ if (!write_data(destination, &fd_data, 1)) {
+ return -1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * Generic test that it works, with various inputs and outputs.
+ * It uses different functions to create the socket and send it and pass
+ * data to it and check it returns correct data back, to see if the run()
+ * parses the commands correctly.
+ */
+void run_test(const char *input_data, const size_t input_size,
+ const char *output_data, const size_t output_size,
+ bool should_succeed = true)
+{
+ // Prepare the input feeder and output checker processes
+ int input_fd(0), output_fd(0);
+ pid_t input(provide_input(&input_fd, input_data, input_size)),
+ output(check_output(&output_fd, output_data, output_size));
+ ASSERT_NE(-1, input) << "Couldn't start input feeder";
+ ASSERT_NE(-1, output) << "Couldn't start output checker";
+ // Run the body
+ int result(run(input_fd, output_fd, get_sock_dummy, send_fd_dummy));
+ // Close the pipes
+ close(input_fd);
+ close(output_fd);
+ // Did it run well?
+ if (should_succeed) {
+ EXPECT_EQ(0, result);
+ } else {
+ EXPECT_NE(0, result);
+ }
+ // Check the subprocesses say everything is OK too
+ EXPECT_TRUE(process_ok(input));
+ EXPECT_TRUE(process_ok(output));
+}
+
+/*
+ * Check it terminates successfully when asked to.
+ */
+TEST(run, terminate) {
+ run_test("T", 1, NULL, 0);
+}
+
+/*
+ * Check it rejects incorrect input.
+ */
+TEST(run, bad_input) {
+ run_test("XXX", 3, "FI", 2, false);
+}
+
+/*
+ * Check it correctly parses queries to create sockets.
+ */
+TEST(run, sockets) {
+ run_test(
+ "SU4\xff\xff\0\0\0\0" // This has 9 bytes
+ "ST4\xff\xff\0\0\0\0" // This has 9 bytes
+ "ST6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
+ "SU6\xff\xff\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" // This has 21 bytes
+ "T", 61,
+ "S\x07S\x05S\x0dS\x0f", 8);
+}
+
+/*
+ * Check if failures of get_socket are handled correctly.
+ */
+TEST(run, bad_sockets) {
+ // We need to construct the answer, but it depends on int length.
+ size_t int_len(sizeof(int));
+ size_t result_len(4 + 2 * int_len);
+ char result[4 + sizeof(int) * 2];
+ // Both errno parts should be 0
+ memset(result, 0, result_len);
+ // Fill the 2 control parts
+ strcpy(result, "EB");
+ strcpy(result + 2 + int_len, "ES");
+ // Run the test
+ run_test(
+ "SU4\xbb\xbb\0\0\0\0"
+ "SU4\xcc\xcc\0\0\0\0"
+ "T", 19,
+ result, result_len);
+}
+
+}
diff --git a/src/bin/stats/b10-stats.8 b/src/bin/stats/b10-stats.8
index 062ff35..5714234 100644
--- a/src/bin/stats/b10-stats.8
+++ b/src/bin/stats/b10-stats.8
@@ -1,7 +1,7 @@
'\" t
.\" Title: b10-stats
.\" Author: [FIXME: author] [see http://docbook.sf.net/el/author]
-.\" Generator: DocBook XSL Stylesheets v1.75.2 <http://docbook.sf.net/>
+.\" Generator: DocBook XSL Stylesheets v1.76.1 <http://docbook.sf.net/>
.\" Date: Oct 15, 2010
.\" Manual: BIND10
.\" Source: BIND10
@@ -9,6 +9,15 @@
.\"
.TH "B10\-STATS" "8" "Oct 15, 2010" "BIND10" "BIND10"
.\" -----------------------------------------------------------------
+.\" * Define some portability stuff
+.\" -----------------------------------------------------------------
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.\" http://bugs.debian.org/507673
+.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html
+.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+.ie \n(.g .ds Aq \(aq
+.el .ds Aq '
+.\" -----------------------------------------------------------------
.\" * set default formatting
.\" -----------------------------------------------------------------
.\" disable hyphenation
@@ -35,6 +44,11 @@ with other modules like
\fBbind10\fR,
\fBb10\-auth\fR
and so on\&. It waits for coming data from other modules, then other modules send data to stats module periodically\&. Other modules send stats data to stats module independently from implementation of stats module, so the frequency of sending data may not be constant\&. Stats module collects data and aggregates it\&.
+\fBb10\-stats\fR
+invokes "sendstats" command for
+\fBbind10\fR
+after its initial starting because it\*(Aqs sure to collect statistics data from
+\fBbind10\fR\&.
.SH "OPTIONS"
.PP
The arguments are as follows:
@@ -49,7 +63,8 @@ switches to verbose mode\&. It sends verbose messages to STDOUT\&.
.PP
/usr/local/share/bind10\-devel/stats\&.spec
\(em This is a spec file for
-\fBb10\-stats\fR\&. It contains definitions of statistics items of BIND 10 and commands received vi bindctl\&.
+\fBb10\-stats\fR\&. It contains definitions of statistics items of BIND 10 and commands received via
+bindctl(1)\&.
.SH "SEE ALSO"
.PP
diff --git a/src/bin/stats/b10-stats.xml b/src/bin/stats/b10-stats.xml
index f622439..7ec58dd 100644
--- a/src/bin/stats/b10-stats.xml
+++ b/src/bin/stats/b10-stats.xml
@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "—">]>
<!--
- - Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2010,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
@@ -64,7 +64,9 @@
send stats data to stats module independently from
implementation of stats module, so the frequency of sending data
may not be constant. Stats module collects data and aggregates
- it.
+ it. <command>b10-stats</command> invokes "sendstats" command
+ for <command>bind10</command> after its initial starting because it's
+ sure to collect statistics data from <command>bind10</command>.
</para>
</refsect1>
diff --git a/src/bin/stats/stats.py.in b/src/bin/stats/stats.py.in
index 15e2980..eeddd92 100755
--- a/src/bin/stats/stats.py.in
+++ b/src/bin/stats/stats.py.in
@@ -1,6 +1,6 @@
#!@PYTHON@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010, 2011 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
@@ -15,8 +15,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-__version__ = "$Revision$"
-
import sys; sys.path.append ('@@PYTHONPATH@@')
import os
import signal
@@ -220,7 +218,13 @@ class CCSessionListener(Listener):
self.stats_data['stats.start_time'] = get_datetime()
self.stats_data['stats.last_update_time'] = get_datetime()
self.stats_data['stats.lname'] = self.session.lname
- return self.cc_session.start()
+ self.cc_session.start()
+ # request Bob to send statistics data
+ if self.verbose:
+ sys.stdout.write("[b10-stats] request Bob to send statistics data\n")
+ cmd = isc.config.ccsession.create_command("sendstats", None)
+ seq = self.session.group_sendmsg(cmd, 'Boss')
+ self.session.group_recvmsg(True, seq)
def stop(self):
"""
diff --git a/src/bin/stats/tests/b10-stats_test.py b/src/bin/stats/tests/b10-stats_test.py
index e4e1a1e..3566bc4 100644
--- a/src/bin/stats/tests/b10-stats_test.py
+++ b/src/bin/stats/tests/b10-stats_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 Internet Systems Consortium.
+# Copyright (C) 2010,2011 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
@@ -13,8 +13,6 @@
# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-__version__ = "$Revision$"
-
#
# Tests for the stats module
#
@@ -504,6 +502,13 @@ class TestStats(unittest.TestCase):
self.assertEqual(result_ok(),
self.session.get_message("Stats", None))
+ def test_for_boss(self):
+ last_queue = self.session.old_message_queue.pop()
+ self.assertEqual(
+ last_queue.msg, {'command': ['sendstats']})
+ self.assertEqual(
+ last_queue.env['group'], 'Boss')
+
class TestStats2(unittest.TestCase):
def setUp(self):
diff --git a/src/bin/tests/process_rename_test.py.in b/src/bin/tests/process_rename_test.py.in
index 81ea085..4b45210 100644
--- a/src/bin/tests/process_rename_test.py.in
+++ b/src/bin/tests/process_rename_test.py.in
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 CZ NIC
+# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/bin/xfrin/xfrin.py.in b/src/bin/xfrin/xfrin.py.in
index fdbc990..10a866e 100755
--- a/src/bin/xfrin/xfrin.py.in
+++ b/src/bin/xfrin/xfrin.py.in
@@ -1,7 +1,6 @@
#!@PYTHON@
# Copyright (C) 2010 Internet Systems Consortium.
-# Copyright (C) 2010 CZ NIC
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/bin/xfrout/tests/Makefile.am b/src/bin/xfrout/tests/Makefile.am
index 01f2e40..76c5977 100644
--- a/src/bin/xfrout/tests/Makefile.am
+++ b/src/bin/xfrout/tests/Makefile.am
@@ -6,7 +6,7 @@ EXTRA_DIST = $(PYTESTS)
# required by loadable python modules.
LIBRARY_PATH_PLACEHOLDER =
if SET_ENV_LIBRARY_PATH
-LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/xfr/.libs:$$$(ENV_LIBRARY_PATH)
+LIBRARY_PATH_PLACEHOLDER += $(ENV_LIBRARY_PATH)=$(abs_top_builddir)/src/lib/dns/.libs:$(abs_top_builddir)/src/lib/exceptions/.libs:$(abs_top_builddir)/src/lib/util/io/.libs:$$$(ENV_LIBRARY_PATH)
endif
# test using command-line arguments, so use check-local target instead of TESTS
@@ -18,7 +18,7 @@ if ENABLE_PYTHON_COVERAGE
endif
for pytest in $(PYTESTS) ; do \
echo Running test: $$pytest ; \
- env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/xfr/.libs \
+ env PYTHONPATH=$(abs_top_builddir)/src/bin/xfrout:$(abs_top_srcdir)/src/lib/python:$(abs_top_builddir)/src/lib/python:$(abs_top_builddir)/src/lib/dns/python/.libs:$(abs_top_builddir)/src/lib/util/io/.libs \
$(LIBRARY_PATH_PLACEHOLDER) \
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
diff --git a/src/bin/xfrout/tests/xfrout_test.py b/src/bin/xfrout/tests/xfrout_test.py
deleted file mode 100644
index 5aec072..0000000
--- a/src/bin/xfrout/tests/xfrout_test.py
+++ /dev/null
@@ -1,437 +0,0 @@
-# Copyright (C) 2010 Internet Systems Consortium.
-#
-# Permission to use, copy, modify, and distribute this software for any
-# purpose with or without fee is hereby granted, provided that the above
-# copyright notice and this permission notice appear in all copies.
-#
-# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
-# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
-# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
-# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
-# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
-# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
-# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-'''Tests for the XfroutSession and UnixSockServer classes '''
-
-
-import unittest
-import os
-from isc.cc.session import *
-from pydnspp import *
-from xfrout import *
-
-# our fake socket, where we can read and insert messages
-class MySocket():
- def __init__(self, family, type):
- self.family = family
- self.type = type
- self.sendqueue = bytearray()
-
- def connect(self, to):
- pass
-
- def close(self):
- pass
-
- def send(self, data):
- self.sendqueue.extend(data);
- return len(data)
-
- def readsent(self):
- if len(self.sendqueue) >= 2:
- size = 2 + struct.unpack("!H", self.sendqueue[:2])[0]
- else:
- size = 0
- result = self.sendqueue[:size]
- self.sendqueue = self.sendqueue[size:]
- return result
-
- def read_msg(self):
- sent_data = self.readsent()
- get_msg = Message(Message.PARSE)
- get_msg.from_wire(bytes(sent_data[2:]))
- return get_msg
-
- def clear_send(self):
- del self.sendqueue[:]
-
-# We subclass the Session class we're testing here, only
-# to override the handle() and _send_data() method
-class MyXfroutSession(XfroutSession):
- def handle(self):
- pass
-
- def _send_data(self, sock, data):
- size = len(data)
- total_count = 0
- while total_count < size:
- count = sock.send(data[total_count:])
- total_count += count
-
-class Dbserver:
- def __init__(self):
- self._shutdown_event = threading.Event()
- def get_db_file(self):
- return None
- def decrease_transfers_counter(self):
- pass
-
-class TestXfroutSession(unittest.TestCase):
- def getmsg(self):
- msg = Message(Message.PARSE)
- msg.from_wire(self.mdata)
- return msg
-
- def setUp(self):
- self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
- self.log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
- self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(), self.log)
- self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
- self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
-
- def test_parse_query_message(self):
- [get_rcode, get_msg] = self.xfrsess._parse_query_message(self.mdata)
- self.assertEqual(get_rcode.to_text(), "NOERROR")
-
- def test_get_query_zone_name(self):
- msg = self.getmsg()
- self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
-
- def test_send_data(self):
- self.xfrsess._send_data(self.sock, self.mdata)
- senddata = self.sock.readsent()
- self.assertEqual(senddata, self.mdata)
-
- def test_reply_xfrout_query_with_error_rcode(self):
- msg = self.getmsg()
- self.xfrsess._reply_query_with_error_rcode(msg, self.sock, Rcode(3))
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
-
- def test_send_message(self):
- msg = self.getmsg()
- msg.make_response()
- # soa record data with different cases
- soa_record = (4, 3, 'Example.com.', 'com.Example.', 3600, 'SOA', None, 'master.Example.com. admin.exAmple.com. 1234 3600 1800 2419200 7200')
- rrset_soa = self.xfrsess._create_rrset_from_db_record(soa_record)
- msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
- self.xfrsess._send_message(self.sock, msg)
- send_out_data = self.sock.readsent()[2:]
-
- # CASE_INSENSITIVE compression mode
- render = MessageRenderer();
- render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
- msg.to_wire(render)
- self.assertNotEqual(render.get_data(), send_out_data)
-
- # CASE_SENSITIVE compression mode
- render.clear()
- render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
- render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
- msg.to_wire(render)
- self.assertEqual(render.get_data(), send_out_data)
-
- def test_clear_message(self):
- msg = self.getmsg()
- qid = msg.get_qid()
- opcode = msg.get_opcode()
- rcode = msg.get_rcode()
-
- self.xfrsess._clear_message(msg)
- self.assertEqual(msg.get_qid(), qid)
- self.assertEqual(msg.get_opcode(), opcode)
- self.assertEqual(msg.get_rcode(), rcode)
- self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
-
- def test_reply_query_with_format_error(self):
- msg = self.getmsg()
- self.xfrsess._reply_query_with_format_error(msg, self.sock)
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
-
- def test_create_rrset_from_db_record(self):
- rrset = self.xfrsess._create_rrset_from_db_record(self.soa_record)
- self.assertEqual(rrset.get_name().to_text(), "example.com.")
- self.assertEqual(rrset.get_class(), RRClass("IN"))
- self.assertEqual(rrset.get_type().to_text(), "SOA")
- rdata = rrset.get_rdata()
- self.assertEqual(rdata[0].to_text(), self.soa_record[7])
-
- def test_send_message_with_last_soa(self):
- rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
-
- msg = self.getmsg()
- msg.make_response()
- self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 0)
- get_msg = self.sock.read_msg()
-
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
-
- #answer_rrset_iter = section_iter(get_msg, section.ANSWER())
- answer = get_msg.get_section(Message.SECTION_ANSWER)[0]#answer_rrset_iter.get_rrset()
- self.assertEqual(answer.get_name().to_text(), "example.com.")
- self.assertEqual(answer.get_class(), RRClass("IN"))
- self.assertEqual(answer.get_type().to_text(), "SOA")
- rdata = answer.get_rdata()
- self.assertEqual(rdata[0].to_text(), self.soa_record[7])
-
- def test_trigger_send_message_with_last_soa(self):
- rrset_a = RRset(Name("example.com"), RRClass.IN(), RRType.A(), RRTTL(3600))
- rrset_a.add_rdata(Rdata(RRType.A(), RRClass.IN(), "192.0.2.1"))
- rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
-
- msg = self.getmsg()
- msg.make_response()
-
- msg.add_rrset(Message.SECTION_ANSWER, rrset_a)
- # give the function a value that is larger than MAX-len(rrset)
- self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 65520)
-
- # this should have triggered the sending of two messages
- # (1 with the rrset we added manually, and 1 that triggered
- # the sending in _with_last_soa)
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
-
- answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
- self.assertEqual(answer.get_name().to_text(), "example.com.")
- self.assertEqual(answer.get_class(), RRClass("IN"))
- self.assertEqual(answer.get_type().to_text(), "A")
- rdata = answer.get_rdata()
- self.assertEqual(rdata[0].to_text(), "192.0.2.1")
-
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 0)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
- self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
-
- #answer_rrset_iter = section_iter(get_msg, Message.SECTION_ANSWER)
- answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
- self.assertEqual(answer.get_name().to_text(), "example.com.")
- self.assertEqual(answer.get_class(), RRClass("IN"))
- self.assertEqual(answer.get_type().to_text(), "SOA")
- rdata = answer.get_rdata()
- self.assertEqual(rdata[0].to_text(), self.soa_record[7])
-
- # and it should not have sent anything else
- self.assertEqual(0, len(self.sock.sendqueue))
-
- def test_get_rrset_len(self):
- rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
- self.assertEqual(82, get_rrset_len(rrset_soa))
-
- def test_zone_has_soa(self):
- global sqlite3_ds
- def mydb1(zone, file):
- return True
- sqlite3_ds.get_zone_soa = mydb1
- self.assertTrue(self.xfrsess._zone_has_soa(""))
- def mydb2(zone, file):
- return False
- sqlite3_ds.get_zone_soa = mydb2
- self.assertFalse(self.xfrsess._zone_has_soa(""))
-
- def test_zone_exist(self):
- global sqlite3_ds
- def zone_exist(zone, file):
- return zone
- sqlite3_ds.zone_exist = zone_exist
- self.assertTrue(self.xfrsess._zone_exist(True))
- self.assertFalse(self.xfrsess._zone_exist(False))
-
- def test_check_xfrout_available(self):
- def zone_exist(zone):
- return zone
- def zone_has_soa(zone):
- return (not zone)
- self.xfrsess._zone_exist = zone_exist
- self.xfrsess._zone_has_soa = zone_has_soa
- self.assertEqual(self.xfrsess._check_xfrout_available(False).to_text(), "NOTAUTH")
- self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "SERVFAIL")
-
- def zone_empty(zone):
- return zone
- self.xfrsess._zone_has_soa = zone_empty
- def false_func():
- return False
- self.xfrsess._server.increase_transfers_counter = false_func
- self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "REFUSED")
- def true_func():
- return True
- self.xfrsess._server.increase_transfers_counter = true_func
- self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "NOERROR")
-
- def test_dns_xfrout_start_formerror(self):
- # formerror
- self.xfrsess.dns_xfrout_start(self.sock, b"\xd6=\x00\x00\x00\x01\x00")
- sent_data = self.sock.readsent()
- self.assertEqual(len(sent_data), 0)
-
- def default(self, param):
- return "example.com"
-
- def test_dns_xfrout_start_notauth(self):
- self.xfrsess._get_query_zone_name = self.default
- def notauth(formpara):
- return Rcode.NOTAUTH()
- self.xfrsess._check_xfrout_available = notauth
- self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
- get_msg = self.sock.read_msg()
- self.assertEqual(get_msg.get_rcode().to_text(), "NOTAUTH")
-
- def test_dns_xfrout_start_noerror(self):
- self.xfrsess._get_query_zone_name = self.default
- def noerror(form):
- return Rcode.NOERROR()
- self.xfrsess._check_xfrout_available = noerror
-
- def myreply(msg, sock, zonename):
- self.sock.send(b"success")
-
- self.xfrsess._reply_xfrout_query = myreply
- self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
- self.assertEqual(self.sock.readsent(), b"success")
-
- def test_reply_xfrout_query_noerror(self):
- global sqlite3_ds
- def get_zone_soa(zonename, file):
- return self.soa_record
-
- def get_zone_datas(zone, file):
- return [self.soa_record]
-
- sqlite3_ds.get_zone_soa = get_zone_soa
- sqlite3_ds.get_zone_datas = get_zone_datas
- self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock, "example.com.")
- reply_msg = self.sock.read_msg()
- self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 2)
-
-class MyCCSession():
- def __init__(self):
- pass
-
- def get_remote_config_value(self, module_name, identifier):
- if module_name == "Auth" and identifier == "database_file":
- return "initdb.file", False
- else:
- return "unknown", False
-
-
-class MyUnixSockServer(UnixSockServer):
- def __init__(self):
- self._lock = threading.Lock()
- self._transfers_counter = 0
- self._shutdown_event = threading.Event()
- self._max_transfers_out = 10
- self._cc = MyCCSession()
- self._log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
-
-class TestUnixSockServer(unittest.TestCase):
- def setUp(self):
- self.write_sock, self.read_sock = socket.socketpair()
- self.unix = MyUnixSockServer()
-
- def test_receive_query_message(self):
- send_msg = b"\xd6=\x00\x00\x00\x01\x00"
- msg_len = struct.pack('H', socket.htons(len(send_msg)))
- self.write_sock.send(msg_len)
- self.write_sock.send(send_msg)
- recv_msg = self.unix._receive_query_message(self.read_sock)
- self.assertEqual(recv_msg, send_msg)
-
- def test_updata_config_data(self):
- self.unix.update_config_data({'transfers_out':10 })
- self.assertEqual(self.unix._max_transfers_out, 10)
-
- def test_get_db_file(self):
- self.assertEqual(self.unix.get_db_file(), "initdb.file")
-
- def test_increase_transfers_counter(self):
- self.unix._max_transfers_out = 10
- count = self.unix._transfers_counter
- self.assertEqual(self.unix.increase_transfers_counter(), True)
- self.assertEqual(count + 1, self.unix._transfers_counter)
-
- self.unix._max_transfers_out = 0
- count = self.unix._transfers_counter
- self.assertEqual(self.unix.increase_transfers_counter(), False)
- self.assertEqual(count, self.unix._transfers_counter)
-
- def test_decrease_transfers_counter(self):
- count = self.unix._transfers_counter
- self.unix.decrease_transfers_counter()
- self.assertEqual(count - 1, self.unix._transfers_counter)
-
- def _remove_file(self, sock_file):
- try:
- os.remove(sock_file)
- except OSError:
- pass
-
- def test_sock_file_in_use_file_exist(self):
- sock_file = 'temp.sock.file'
- self._remove_file(sock_file)
- self.assertFalse(self.unix._sock_file_in_use(sock_file))
- self.assertFalse(os.path.exists(sock_file))
-
- def test_sock_file_in_use_file_not_exist(self):
- self.assertFalse(self.unix._sock_file_in_use('temp.sock.file'))
-
- def _start_unix_sock_server(self, sock_file):
- serv = ThreadingUnixStreamServer(sock_file, BaseRequestHandler)
- serv_thread = threading.Thread(target=serv.serve_forever)
- serv_thread.setDaemon(True)
- serv_thread.start()
-
- def test_sock_file_in_use(self):
- sock_file = 'temp.sock.file'
- self._remove_file(sock_file)
- self.assertFalse(self.unix._sock_file_in_use(sock_file))
- self._start_unix_sock_server(sock_file)
-
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
- self.assertTrue(self.unix._sock_file_in_use(sock_file))
- sys.stdout = old_stdout
-
- def test_remove_unused_sock_file_in_use(self):
- sock_file = 'temp.sock.file'
- self._remove_file(sock_file)
- self.assertFalse(self.unix._sock_file_in_use(sock_file))
- self._start_unix_sock_server(sock_file)
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
- try:
- self.unix._remove_unused_sock_file(sock_file)
- except SystemExit:
- pass
- else:
- # This should never happen
- self.assertTrue(False)
-
- sys.stdout = old_stdout
-
- def test_remove_unused_sock_file_dir(self):
- import tempfile
- dir_name = tempfile.mkdtemp()
- old_stdout = sys.stdout
- sys.stdout = open(os.devnull, 'w')
- try:
- self.unix._remove_unused_sock_file(dir_name)
- except SystemExit:
- pass
- else:
- # This should never happen
- self.assertTrue(False)
-
- sys.stdout = old_stdout
- os.rmdir(dir_name)
-
-if __name__== "__main__":
- unittest.main()
diff --git a/src/bin/xfrout/tests/xfrout_test.py.in b/src/bin/xfrout/tests/xfrout_test.py.in
new file mode 100644
index 0000000..472ef3c
--- /dev/null
+++ b/src/bin/xfrout/tests/xfrout_test.py.in
@@ -0,0 +1,469 @@
+# 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.
+
+'''Tests for the XfroutSession and UnixSockServer classes '''
+
+
+import unittest
+import os
+from isc.cc.session import *
+from pydnspp import *
+from xfrout import *
+import xfrout
+
+# our fake socket, where we can read and insert messages
+class MySocket():
+ def __init__(self, family, type):
+ self.family = family
+ self.type = type
+ self.sendqueue = bytearray()
+
+ def connect(self, to):
+ pass
+
+ def close(self):
+ pass
+
+ def send(self, data):
+ self.sendqueue.extend(data);
+ return len(data)
+
+ def readsent(self):
+ if len(self.sendqueue) >= 2:
+ size = 2 + struct.unpack("!H", self.sendqueue[:2])[0]
+ else:
+ size = 0
+ result = self.sendqueue[:size]
+ self.sendqueue = self.sendqueue[size:]
+ return result
+
+ def read_msg(self):
+ sent_data = self.readsent()
+ get_msg = Message(Message.PARSE)
+ get_msg.from_wire(bytes(sent_data[2:]))
+ return get_msg
+
+ def clear_send(self):
+ del self.sendqueue[:]
+
+# We subclass the Session class we're testing here, only
+# to override the handle() and _send_data() method
+class MyXfroutSession(XfroutSession):
+ def handle(self):
+ pass
+
+ def _send_data(self, sock, data):
+ size = len(data)
+ total_count = 0
+ while total_count < size:
+ count = sock.send(data[total_count:])
+ total_count += count
+
+class Dbserver:
+ def __init__(self):
+ self._shutdown_event = threading.Event()
+ def get_db_file(self):
+ return None
+ def decrease_transfers_counter(self):
+ pass
+
+class TestXfroutSession(unittest.TestCase):
+ def getmsg(self):
+ msg = Message(Message.PARSE)
+ msg.from_wire(self.mdata)
+ return msg
+
+ def setUp(self):
+ self.sock = MySocket(socket.AF_INET,socket.SOCK_STREAM)
+ self.log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
+ self.xfrsess = MyXfroutSession(self.sock, None, Dbserver(), self.log)
+ self.mdata = bytes(b'\xd6=\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x07example\x03com\x00\x00\xfc\x00\x01')
+ self.soa_record = (4, 3, 'example.com.', 'com.example.', 3600, 'SOA', None, 'master.example.com. admin.example.com. 1234 3600 1800 2419200 7200')
+
+ def test_parse_query_message(self):
+ [get_rcode, get_msg] = self.xfrsess._parse_query_message(self.mdata)
+ self.assertEqual(get_rcode.to_text(), "NOERROR")
+
+ def test_get_query_zone_name(self):
+ msg = self.getmsg()
+ self.assertEqual(self.xfrsess._get_query_zone_name(msg), "example.com.")
+
+ def test_send_data(self):
+ self.xfrsess._send_data(self.sock, self.mdata)
+ senddata = self.sock.readsent()
+ self.assertEqual(senddata, self.mdata)
+
+ def test_reply_xfrout_query_with_error_rcode(self):
+ msg = self.getmsg()
+ self.xfrsess._reply_query_with_error_rcode(msg, self.sock, Rcode(3))
+ get_msg = self.sock.read_msg()
+ self.assertEqual(get_msg.get_rcode().to_text(), "NXDOMAIN")
+
+ def test_send_message(self):
+ msg = self.getmsg()
+ msg.make_response()
+ # soa record data with different cases
+ soa_record = (4, 3, 'Example.com.', 'com.Example.', 3600, 'SOA', None, 'master.Example.com. admin.exAmple.com. 1234 3600 1800 2419200 7200')
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(soa_record)
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_soa)
+ self.xfrsess._send_message(self.sock, msg)
+ send_out_data = self.sock.readsent()[2:]
+
+ # CASE_INSENSITIVE compression mode
+ render = MessageRenderer();
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertNotEqual(render.get_data(), send_out_data)
+
+ # CASE_SENSITIVE compression mode
+ render.clear()
+ render.set_compress_mode(MessageRenderer.CASE_SENSITIVE)
+ render.set_length_limit(XFROUT_MAX_MESSAGE_SIZE)
+ msg.to_wire(render)
+ self.assertEqual(render.get_data(), send_out_data)
+
+ def test_clear_message(self):
+ msg = self.getmsg()
+ qid = msg.get_qid()
+ opcode = msg.get_opcode()
+ rcode = msg.get_rcode()
+
+ self.xfrsess._clear_message(msg)
+ self.assertEqual(msg.get_qid(), qid)
+ self.assertEqual(msg.get_opcode(), opcode)
+ self.assertEqual(msg.get_rcode(), rcode)
+ self.assertTrue(msg.get_header_flag(Message.HEADERFLAG_AA))
+
+ def test_reply_query_with_format_error(self):
+ msg = self.getmsg()
+ self.xfrsess._reply_query_with_format_error(msg, self.sock)
+ get_msg = self.sock.read_msg()
+ self.assertEqual(get_msg.get_rcode().to_text(), "FORMERR")
+
+ def test_create_rrset_from_db_record(self):
+ rrset = self.xfrsess._create_rrset_from_db_record(self.soa_record)
+ self.assertEqual(rrset.get_name().to_text(), "example.com.")
+ self.assertEqual(rrset.get_class(), RRClass("IN"))
+ self.assertEqual(rrset.get_type().to_text(), "SOA")
+ rdata = rrset.get_rdata()
+ self.assertEqual(rdata[0].to_text(), self.soa_record[7])
+
+ def test_send_message_with_last_soa(self):
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
+
+ msg = self.getmsg()
+ msg.make_response()
+ self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 0)
+ get_msg = self.sock.read_msg()
+
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
+
+ #answer_rrset_iter = section_iter(get_msg, section.ANSWER())
+ answer = get_msg.get_section(Message.SECTION_ANSWER)[0]#answer_rrset_iter.get_rrset()
+ self.assertEqual(answer.get_name().to_text(), "example.com.")
+ self.assertEqual(answer.get_class(), RRClass("IN"))
+ self.assertEqual(answer.get_type().to_text(), "SOA")
+ rdata = answer.get_rdata()
+ self.assertEqual(rdata[0].to_text(), self.soa_record[7])
+
+ def test_trigger_send_message_with_last_soa(self):
+ rrset_a = RRset(Name("example.com"), RRClass.IN(), RRType.A(), RRTTL(3600))
+ rrset_a.add_rdata(Rdata(RRType.A(), RRClass.IN(), "192.0.2.1"))
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
+
+ msg = self.getmsg()
+ msg.make_response()
+
+ msg.add_rrset(Message.SECTION_ANSWER, rrset_a)
+ # give the function a value that is larger than MAX-len(rrset)
+ self.xfrsess._send_message_with_last_soa(msg, self.sock, rrset_soa, 65520)
+
+ # this should have triggered the sending of two messages
+ # (1 with the rrset we added manually, and 1 that triggered
+ # the sending in _with_last_soa)
+ get_msg = self.sock.read_msg()
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 1)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
+
+ answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
+ self.assertEqual(answer.get_name().to_text(), "example.com.")
+ self.assertEqual(answer.get_class(), RRClass("IN"))
+ self.assertEqual(answer.get_type().to_text(), "A")
+ rdata = answer.get_rdata()
+ self.assertEqual(rdata[0].to_text(), "192.0.2.1")
+
+ get_msg = self.sock.read_msg()
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_QUESTION), 0)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_ANSWER), 1)
+ self.assertEqual(get_msg.get_rr_count(Message.SECTION_AUTHORITY), 0)
+
+ #answer_rrset_iter = section_iter(get_msg, Message.SECTION_ANSWER)
+ answer = get_msg.get_section(Message.SECTION_ANSWER)[0]
+ self.assertEqual(answer.get_name().to_text(), "example.com.")
+ self.assertEqual(answer.get_class(), RRClass("IN"))
+ self.assertEqual(answer.get_type().to_text(), "SOA")
+ rdata = answer.get_rdata()
+ self.assertEqual(rdata[0].to_text(), self.soa_record[7])
+
+ # and it should not have sent anything else
+ self.assertEqual(0, len(self.sock.sendqueue))
+
+ def test_get_rrset_len(self):
+ rrset_soa = self.xfrsess._create_rrset_from_db_record(self.soa_record)
+ self.assertEqual(82, get_rrset_len(rrset_soa))
+
+ def test_zone_has_soa(self):
+ global sqlite3_ds
+ def mydb1(zone, file):
+ return True
+ sqlite3_ds.get_zone_soa = mydb1
+ self.assertTrue(self.xfrsess._zone_has_soa(""))
+ def mydb2(zone, file):
+ return False
+ sqlite3_ds.get_zone_soa = mydb2
+ self.assertFalse(self.xfrsess._zone_has_soa(""))
+
+ def test_zone_exist(self):
+ global sqlite3_ds
+ def zone_exist(zone, file):
+ return zone
+ sqlite3_ds.zone_exist = zone_exist
+ self.assertTrue(self.xfrsess._zone_exist(True))
+ self.assertFalse(self.xfrsess._zone_exist(False))
+
+ def test_check_xfrout_available(self):
+ def zone_exist(zone):
+ return zone
+ def zone_has_soa(zone):
+ return (not zone)
+ self.xfrsess._zone_exist = zone_exist
+ self.xfrsess._zone_has_soa = zone_has_soa
+ self.assertEqual(self.xfrsess._check_xfrout_available(False).to_text(), "NOTAUTH")
+ self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "SERVFAIL")
+
+ def zone_empty(zone):
+ return zone
+ self.xfrsess._zone_has_soa = zone_empty
+ def false_func():
+ return False
+ self.xfrsess._server.increase_transfers_counter = false_func
+ self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "REFUSED")
+ def true_func():
+ return True
+ self.xfrsess._server.increase_transfers_counter = true_func
+ self.assertEqual(self.xfrsess._check_xfrout_available(True).to_text(), "NOERROR")
+
+ def test_dns_xfrout_start_formerror(self):
+ # formerror
+ self.xfrsess.dns_xfrout_start(self.sock, b"\xd6=\x00\x00\x00\x01\x00")
+ sent_data = self.sock.readsent()
+ self.assertEqual(len(sent_data), 0)
+
+ def default(self, param):
+ return "example.com"
+
+ def test_dns_xfrout_start_notauth(self):
+ self.xfrsess._get_query_zone_name = self.default
+ def notauth(formpara):
+ return Rcode.NOTAUTH()
+ self.xfrsess._check_xfrout_available = notauth
+ self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
+ get_msg = self.sock.read_msg()
+ self.assertEqual(get_msg.get_rcode().to_text(), "NOTAUTH")
+
+ def test_dns_xfrout_start_noerror(self):
+ self.xfrsess._get_query_zone_name = self.default
+ def noerror(form):
+ return Rcode.NOERROR()
+ self.xfrsess._check_xfrout_available = noerror
+
+ def myreply(msg, sock, zonename):
+ self.sock.send(b"success")
+
+ self.xfrsess._reply_xfrout_query = myreply
+ self.xfrsess.dns_xfrout_start(self.sock, self.mdata)
+ self.assertEqual(self.sock.readsent(), b"success")
+
+ def test_reply_xfrout_query_noerror(self):
+ global sqlite3_ds
+ def get_zone_soa(zonename, file):
+ return self.soa_record
+
+ def get_zone_datas(zone, file):
+ return [self.soa_record]
+
+ sqlite3_ds.get_zone_soa = get_zone_soa
+ sqlite3_ds.get_zone_datas = get_zone_datas
+ self.xfrsess._reply_xfrout_query(self.getmsg(), self.sock, "example.com.")
+ reply_msg = self.sock.read_msg()
+ self.assertEqual(reply_msg.get_rr_count(Message.SECTION_ANSWER), 2)
+
+class MyCCSession():
+ def __init__(self):
+ pass
+
+ def get_remote_config_value(self, module_name, identifier):
+ if module_name == "Auth" and identifier == "database_file":
+ return "initdb.file", False
+ else:
+ return "unknown", False
+
+
+class MyUnixSockServer(UnixSockServer):
+ def __init__(self):
+ self._lock = threading.Lock()
+ self._transfers_counter = 0
+ self._shutdown_event = threading.Event()
+ self._max_transfers_out = 10
+ self._cc = MyCCSession()
+ self._log = isc.log.NSLogger('xfrout', '', severity = 'critical', log_to_console = False )
+
+class TestUnixSockServer(unittest.TestCase):
+ def setUp(self):
+ self.write_sock, self.read_sock = socket.socketpair()
+ self.unix = MyUnixSockServer()
+
+ def test_receive_query_message(self):
+ send_msg = b"\xd6=\x00\x00\x00\x01\x00"
+ msg_len = struct.pack('H', socket.htons(len(send_msg)))
+ self.write_sock.send(msg_len)
+ self.write_sock.send(send_msg)
+ recv_msg = self.unix._receive_query_message(self.read_sock)
+ self.assertEqual(recv_msg, send_msg)
+
+ def test_updata_config_data(self):
+ self.unix.update_config_data({'transfers_out':10 })
+ self.assertEqual(self.unix._max_transfers_out, 10)
+
+ def test_get_db_file(self):
+ self.assertEqual(self.unix.get_db_file(), "initdb.file")
+
+ def test_increase_transfers_counter(self):
+ self.unix._max_transfers_out = 10
+ count = self.unix._transfers_counter
+ self.assertEqual(self.unix.increase_transfers_counter(), True)
+ self.assertEqual(count + 1, self.unix._transfers_counter)
+
+ self.unix._max_transfers_out = 0
+ count = self.unix._transfers_counter
+ self.assertEqual(self.unix.increase_transfers_counter(), False)
+ self.assertEqual(count, self.unix._transfers_counter)
+
+ def test_decrease_transfers_counter(self):
+ count = self.unix._transfers_counter
+ self.unix.decrease_transfers_counter()
+ self.assertEqual(count - 1, self.unix._transfers_counter)
+
+ def _remove_file(self, sock_file):
+ try:
+ os.remove(sock_file)
+ except OSError:
+ pass
+
+ def test_sock_file_in_use_file_exist(self):
+ sock_file = 'temp.sock.file'
+ self._remove_file(sock_file)
+ self.assertFalse(self.unix._sock_file_in_use(sock_file))
+ self.assertFalse(os.path.exists(sock_file))
+
+ def test_sock_file_in_use_file_not_exist(self):
+ self.assertFalse(self.unix._sock_file_in_use('temp.sock.file'))
+
+ def _start_unix_sock_server(self, sock_file):
+ serv = ThreadingUnixStreamServer(sock_file, BaseRequestHandler)
+ serv_thread = threading.Thread(target=serv.serve_forever)
+ serv_thread.setDaemon(True)
+ serv_thread.start()
+
+ def test_sock_file_in_use(self):
+ sock_file = 'temp.sock.file'
+ self._remove_file(sock_file)
+ self.assertFalse(self.unix._sock_file_in_use(sock_file))
+ self._start_unix_sock_server(sock_file)
+
+ old_stdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
+ self.assertTrue(self.unix._sock_file_in_use(sock_file))
+ sys.stdout = old_stdout
+
+ def test_remove_unused_sock_file_in_use(self):
+ sock_file = 'temp.sock.file'
+ self._remove_file(sock_file)
+ self.assertFalse(self.unix._sock_file_in_use(sock_file))
+ self._start_unix_sock_server(sock_file)
+ old_stdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
+ try:
+ self.unix._remove_unused_sock_file(sock_file)
+ except SystemExit:
+ pass
+ else:
+ # This should never happen
+ self.assertTrue(False)
+
+ sys.stdout = old_stdout
+
+ def test_remove_unused_sock_file_dir(self):
+ import tempfile
+ dir_name = tempfile.mkdtemp()
+ old_stdout = sys.stdout
+ sys.stdout = open(os.devnull, 'w')
+ try:
+ self.unix._remove_unused_sock_file(dir_name)
+ except SystemExit:
+ pass
+ else:
+ # This should never happen
+ self.assertTrue(False)
+
+ sys.stdout = old_stdout
+ os.rmdir(dir_name)
+
+class TestInitialization(unittest.TestCase):
+ def setEnv(self, name, value):
+ if value is None:
+ if name in os.environ:
+ del os.environ[name]
+ else:
+ os.environ[name] = value
+
+ def setUp(self):
+ self._oldSocket = os.getenv("BIND10_XFROUT_SOCKET_FILE")
+ self._oldFromBuild = os.getenv("B10_FROM_BUILD")
+
+ def tearDown(self):
+ self.setEnv("B10_FROM_BUILD", self._oldFromBuild)
+ self.setEnv("BIND10_XFROUT_SOCKET_FILE", self._oldSocket)
+ # Make sure even the computed values are back
+ xfrout.init_paths()
+
+ def testNoEnv(self):
+ self.setEnv("B10_FROM_BUILD", None)
+ self.setEnv("BIND10_XFROUT_SOCKET_FILE", None)
+ xfrout.init_paths()
+ self.assertEqual(xfrout.UNIX_SOCKET_FILE,
+ "@@LOCALSTATEDIR@@/auth_xfrout_conn")
+
+ def testProvidedSocket(self):
+ self.setEnv("B10_FROM_BUILD", None)
+ self.setEnv("BIND10_XFROUT_SOCKET_FILE", "The/Socket/File")
+ xfrout.init_paths()
+ self.assertEqual(xfrout.UNIX_SOCKET_FILE, "The/Socket/File")
+
+if __name__== "__main__":
+ unittest.main()
diff --git a/src/bin/xfrout/xfrout.py.in b/src/bin/xfrout/xfrout.py.in
index f420d4b..17ca3eb 100755
--- a/src/bin/xfrout/xfrout.py.in
+++ b/src/bin/xfrout/xfrout.py.in
@@ -1,7 +1,6 @@
#!@PYTHON@
# Copyright (C) 2010 Internet Systems Consortium.
-# Copyright (C) 2010 CZ NIC
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -38,29 +37,38 @@ from optparse import OptionParser, OptionValueError
from isc.util import socketserver_mixin
try:
- from libxfr_python import *
+ from libutil_io_python import *
from pydnspp import *
except ImportError as e:
# C++ loadable module may not be installed; even so the xfrout process
# must keep running, so we warn about it and move forward.
- sys.stderr.write('[b10-xfrout] failed to import DNS or XFR module: %s\n' % str(e))
+ sys.stderr.write('[b10-xfrout] failed to import DNS or isc.util.io module: %s\n' % str(e))
isc.util.process.rename()
-if "B10_FROM_BUILD" in os.environ:
- SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrout"
- AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
- if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
- UNIX_SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
- "/auth_xfrout_conn"
+def init_paths():
+ global SPECFILE_PATH
+ global AUTH_SPECFILE_PATH
+ global UNIX_SOCKET_FILE
+ if "B10_FROM_BUILD" in os.environ:
+ SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/xfrout"
+ AUTH_SPECFILE_PATH = os.environ["B10_FROM_BUILD"] + "/src/bin/auth"
+ if "B10_FROM_SOURCE_LOCALSTATEDIR" in os.environ:
+ UNIX_SOCKET_FILE = os.environ["B10_FROM_SOURCE_LOCALSTATEDIR"] + \
+ "/auth_xfrout_conn"
+ else:
+ UNIX_SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn"
else:
- UNIX_SOCKET_FILE = os.environ["B10_FROM_BUILD"] + "/auth_xfrout_conn"
-else:
- PREFIX = "@prefix@"
- DATAROOTDIR = "@datarootdir@"
- SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
- AUTH_SPECFILE_PATH = SPECFILE_PATH
- UNIX_SOCKET_FILE = "@@LOCALSTATEDIR@@/auth_xfrout_conn"
+ PREFIX = "@prefix@"
+ DATAROOTDIR = "@datarootdir@"
+ SPECFILE_PATH = "@datadir@/@PACKAGE@".replace("${datarootdir}", DATAROOTDIR).replace("${prefix}", PREFIX)
+ AUTH_SPECFILE_PATH = SPECFILE_PATH
+ if "BIND10_XFROUT_SOCKET_FILE" in os.environ:
+ UNIX_SOCKET_FILE = os.environ["BIND10_XFROUT_SOCKET_FILE"]
+ else:
+ UNIX_SOCKET_FILE = "@@LOCALSTATEDIR@@/auth_xfrout_conn"
+
+init_paths()
SPECFILE_LOCATION = SPECFILE_PATH + "/xfrout.spec"
AUTH_SPECFILE_LOCATION = AUTH_SPECFILE_PATH + os.sep + "auth.spec"
@@ -377,7 +385,7 @@ class UnixSockServer(socketserver_mixin.NoPollMixIn, ThreadingUnixStreamServer):
# This may happen when one xfrout process try to connect to
# xfrout unix socket server, to check whether there is another
# xfrout running.
- if sock_fd == XFR_FD_RECEIVE_FAIL:
+ if sock_fd == FD_COMM_ERROR:
self._log.log_message("error", "Failed to receive the file descriptor for XFR connection")
return
diff --git a/src/bin/zonemgr/tests/zonemgr_test.py b/src/bin/zonemgr/tests/zonemgr_test.py
index 70dc1b0..2e9f685 100644
--- a/src/bin/zonemgr/tests/zonemgr_test.py
+++ b/src/bin/zonemgr/tests/zonemgr_test.py
@@ -22,10 +22,10 @@ import tempfile
from zonemgr import *
ZONE_NAME_CLASS1_IN = ("sd.cn.", "IN")
-ZONE_NAME_CLASS2_CH = ("tw.cn", "CH")
+ZONE_NAME_CLASS2_CH = ("tw.cn.", "CH")
ZONE_NAME_CLASS3_IN = ("example.com", "IN")
ZONE_NAME_CLASS1_CH = ("sd.cn.", "CH")
-ZONE_NAME_CLASS2_IN = ("tw.cn", "IN")
+ZONE_NAME_CLASS2_IN = ("tw.cn.", "IN")
MAX_TRANSFER_TIMEOUT = 14400
LOWERBOUND_REFRESH = 10
@@ -46,21 +46,43 @@ class MySession():
def group_recvmsg(self, nonblock, seq):
return None, None
+class FakeConfig:
+ def __init__(self):
+ self.zone_list = []
+ self.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN,
+ ZONE_NAME_CLASS2_CH])
+ def set_zone_list_from_name_classes(self, zones):
+ self.zone_list = map(lambda nc: {"name": nc[0], "class": nc[1]}, zones)
+ def get(self, name):
+ if name == 'lowerbound_refresh':
+ return LOWERBOUND_REFRESH
+ elif name == 'lowerbound_retry':
+ return LOWERBOUND_RETRY
+ elif name == 'max_transfer_timeout':
+ return MAX_TRANSFER_TIMEOUT
+ elif name == 'jitter_scope':
+ return JITTER_SCOPE
+ elif name == 'secondary_zones':
+ return self.zone_list
+ else:
+ raise ValueError('Uknown config option')
+
class MyZonemgrRefresh(ZonemgrRefresh):
def __init__(self):
- class FakeConfig:
- def get(self, name):
- if name == 'lowerbound_refresh':
- return LOWERBOUND_REFRESH
- elif name == 'lowerbound_retry':
- return LOWERBOUND_RETRY
- elif name == 'max_transfer_timeout':
- return MAX_TRANSFER_TIMEOUT
- elif name == 'jitter_scope':
- return JITTER_SCOPE
- else:
- raise ValueError('Uknown config option')
self._master_socket, self._slave_socket = socket.socketpair()
+ self._zonemgr_refresh_info = {}
+
+ def get_zone_soa(zone_name, db_file):
+ if zone_name == 'sd.cn.':
+ return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
+ 'a.dns.cn. root.cnnic.cn. 2009073106 7200 3600 2419200 21600')
+ elif zone_name == 'tw.cn.':
+ return (1, 2, 'tw.cn.', 'cn.sd.', 21600, 'SOA', None,
+ 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600')
+ else:
+ return None
+ sqlite3_ds.get_zone_soa = get_zone_soa
+
ZonemgrRefresh.__init__(self, MySession(), "initdb.file",
self._slave_socket, FakeConfig())
current_time = time.time()
@@ -70,7 +92,7 @@ class MyZonemgrRefresh(ZonemgrRefresh):
'next_refresh_time': current_time + 6500,
'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
'zone_state': 0},
- ('tw.cn', 'CH'): {
+ ('tw.cn.', 'CH'): {
'last_refresh_time': current_time,
'next_refresh_time': current_time + 6900,
'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073112 7200 3600 2419200 21600',
@@ -272,28 +294,6 @@ class TestZonemgrRefresh(unittest.TestCase):
ZONE_NAME_CLASS1_IN)
sqlite3_ds.get_zone_soa = old_get_zone_soa
- def test_build_zonemgr_refresh_info(self):
- soa_rdata = 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600'
-
- def get_zones_info(db_file):
- return [("sd.cn.", "IN")]
-
- def get_zone_soa(zone_name, db_file):
- return (1, 2, 'sd.cn.', 'cn.sd.', 21600, 'SOA', None,
- 'a.dns.cn. root.cnnic.cn. 2009073106 1800 900 2419200 21600')
-
- sqlite3_ds.get_zones_info = get_zones_info
- sqlite3_ds.get_zone_soa = get_zone_soa
-
- self.zone_refresh._zonemgr_refresh_info = {}
- self.zone_refresh._build_zonemgr_refresh_info()
- self.assertEqual(1, len(self.zone_refresh._zonemgr_refresh_info))
- zone_soa_rdata = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_soa_rdata"]
- self.assertEqual(soa_rdata, zone_soa_rdata)
- self.assertEqual(ZONE_OK, self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["zone_state"])
- self.assertTrue("last_refresh_time" in self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN].keys())
- self.assertTrue("next_refresh_time" in self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN].keys())
-
def test_zone_handle_notify(self):
self.zone_refresh.zone_handle_notify(ZONE_NAME_CLASS1_IN,"127.0.0.1")
notify_master = self.zone_refresh._zonemgr_refresh_info[ZONE_NAME_CLASS1_IN]["notify_master"]
@@ -356,7 +356,7 @@ class TestZonemgrRefresh(unittest.TestCase):
'next_refresh_time': time1 + 7200,
'zone_soa_rdata': 'a.dns.cn. root.cnnic.cn. 2009073105 7200 3600 2419200 21600',
'zone_state': ZONE_OK},
- ("tw.cn","CH"):{
+ ("tw.cn.","CH"):{
'last_refresh_time': time1 - 7200,
'next_refresh_time': time1,
'refresh_timeout': time1 + MAX_TRANSFER_TIMEOUT,
@@ -424,7 +424,8 @@ class TestZonemgrRefresh(unittest.TestCase):
"lowerbound_refresh" : 60,
"lowerbound_retry" : 30,
"max_transfer_timeout" : 19800,
- "jitter_scope" : 0.25
+ "jitter_scope" : 0.25,
+ "secondary_zones": []
}
self.zone_refresh.update_config_data(config_data)
self.assertEqual(60, self.zone_refresh._lowerbound_refresh)
@@ -440,6 +441,31 @@ class TestZonemgrRefresh(unittest.TestCase):
self.zone_refresh.shutdown()
self.assertFalse(listener.is_alive())
+ def test_secondary_zones(self):
+ """Test that we can modify the list of secondary zones"""
+ config = FakeConfig()
+ config.zone_list = []
+ # First, remove everything
+ self.zone_refresh.update_config_data(config)
+ self.assertEqual(self.zone_refresh._zonemgr_refresh_info, {})
+ # Put something in
+ config.set_zone_list_from_name_classes([ZONE_NAME_CLASS1_IN])
+ self.zone_refresh.update_config_data(config)
+ self.assertTrue(("sd.cn.", "IN") in
+ self.zone_refresh._zonemgr_refresh_info)
+ # This one does not exist
+ config.set_zone_list_from_name_classes(["example.net", "CH"])
+ self.assertRaises(ZonemgrException,
+ self.zone_refresh.update_config_data, config)
+ # So it should not affect the old ones
+ self.assertTrue(("sd.cn.", "IN") in
+ self.zone_refresh._zonemgr_refresh_info)
+ # Make sure it works even when we "accidentally" forget the final dot
+ config.set_zone_list_from_name_classes([("sd.cn", "IN")])
+ self.zone_refresh.update_config_data(config)
+ self.assertTrue(("sd.cn.", "IN") in
+ self.zone_refresh._zonemgr_refresh_info)
+
def tearDown(self):
sys.stderr= self.stderr_backup
@@ -464,10 +490,11 @@ class MyZonemgr(Zonemgr):
self._cc = MySession()
self._module_cc = MyCCSession()
self._config_data = {
- "lowerbound_refresh" : 10,
- "lowerbound_retry" : 5,
+ "lowerbound_refresh" : 10,
+ "lowerbound_retry" : 5,
"max_transfer_timeout" : 14400,
- "jitter_scope" : 0.1
+ "jitter_scope" : 0.1,
+ "secondary_zones": []
}
def _start_zone_refresh_timer(self):
@@ -480,12 +507,14 @@ class TestZonemgr(unittest.TestCase):
def test_config_handler(self):
config_data1 = {
- "lowerbound_refresh" : 60,
- "lowerbound_retry" : 30,
+ "lowerbound_refresh" : 60,
+ "lowerbound_retry" : 30,
"max_transfer_timeout" : 14400,
- "jitter_scope" : 0.1
+ "jitter_scope" : 0.1,
+ "secondary_zones": []
}
- self.zonemgr.config_handler(config_data1)
+ self.assertEqual(self.zonemgr.config_handler(config_data1),
+ {"result": [0]})
self.assertEqual(config_data1, self.zonemgr._config_data)
config_data2 = {"zone_name" : "sd.cn.", "port" : "53", "master" : "192.168.1.1"}
self.zonemgr.config_handler(config_data2)
@@ -494,10 +523,19 @@ class TestZonemgr(unittest.TestCase):
config_data3 = {"jitter_scope" : 0.7}
self.zonemgr.config_handler(config_data3)
self.assertEqual(0.5, self.zonemgr._config_data.get("jitter_scope"))
+ # The zone doesn't exist in database, it should be rejected
+ self.zonemgr._zone_refresh = ZonemgrRefresh(None, "initdb.file", None,
+ config_data1)
+ config_data1["secondary_zones"] = [{"name": "nonexistent.example",
+ "class": "IN"}]
+ self.assertNotEqual(self.zonemgr.config_handler(config_data1),
+ {"result": [0]})
+ # As it is rejected, the old value should be kept
+ self.assertEqual(0.5, self.zonemgr._config_data.get("jitter_scope"))
def test_get_db_file(self):
self.assertEqual("initdb.file", self.zonemgr.get_db_file())
-
+
def test_parse_cmd_params(self):
params1 = {"zone_name" : "org.cn", "zone_class" : "CH", "master" : "127.0.0.1"}
answer1 = (("org.cn", "CH"), "127.0.0.1")
diff --git a/src/bin/zonemgr/zonemgr.py.in b/src/bin/zonemgr/zonemgr.py.in
index f9659a3..129c673 100755
--- a/src/bin/zonemgr/zonemgr.py.in
+++ b/src/bin/zonemgr/zonemgr.py.in
@@ -1,7 +1,6 @@
#!@PYTHON@
# Copyright (C) 2010 Internet Systems Consortium.
-# Copyright (C) 2010 CZ NIC
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -101,9 +100,8 @@ class ZonemgrRefresh:
self._cc = cc
self._check_sock = slave_socket
self._db_file = db_file
- self.update_config_data(config_data)
self._zonemgr_refresh_info = {}
- self._build_zonemgr_refresh_info()
+ self.update_config_data(config_data)
self._running = False
def _random_jitter(self, max, jitter):
@@ -149,16 +147,13 @@ class ZonemgrRefresh:
def _zone_not_exist(self, zone_name_class):
""" Zone doesn't belong to zonemgr"""
- if zone_name_class in self._zonemgr_refresh_info.keys():
- return False
- return True
+ return not zone_name_class in self._zonemgr_refresh_info
def zone_refresh_success(self, zone_name_class):
"""Update zone info after zone refresh success"""
if (self._zone_not_exist(zone_name_class)):
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
- return
self.zonemgr_reload_zone(zone_name_class)
self._set_zone_refresh_timer(zone_name_class)
self._set_zone_state(zone_name_class, ZONE_OK)
@@ -169,7 +164,6 @@ class ZonemgrRefresh:
if (self._zone_not_exist(zone_name_class)):
raise ZonemgrException("[b10-zonemgr] Zone (%s, %s) doesn't "
"belong to zonemgr" % zone_name_class)
- return
# Is zone expired?
if (self._zone_is_expired(zone_name_class)):
self._set_zone_state(zone_name_class, ZONE_EXPIRED)
@@ -182,7 +176,6 @@ class ZonemgrRefresh:
if (self._zone_not_exist(zone_name_class)):
raise ZonemgrException("[b10-zonemgr] Notified zone (%s, %s) "
"doesn't belong to zonemgr" % zone_name_class)
- return
self._set_zone_notifier_master(zone_name_class, master)
self._set_zone_notify_timer(zone_name_class)
@@ -193,6 +186,7 @@ class ZonemgrRefresh:
def zonemgr_add_zone(self, zone_name_class):
""" Add a zone into zone manager."""
+ log_msg("Loading zone (%s, %s)" % zone_name_class)
zone_info = {}
zone_soa = sqlite3_ds.get_zone_soa(str(zone_name_class[0]), self._db_file)
if not zone_soa:
@@ -204,14 +198,6 @@ class ZonemgrRefresh:
float(zone_soa[7].split(" ")[REFRESH_OFFSET])
self._zonemgr_refresh_info[zone_name_class] = zone_info
- def _build_zonemgr_refresh_info(self):
- """ Build zonemgr refresh info map."""
- log_msg("Start loading zone into zonemgr.")
- for zone_name, zone_class in sqlite3_ds.get_zones_info(self._db_file):
- zone_name_class = (zone_name, zone_class)
- self.zonemgr_add_zone(zone_name_class)
- log_msg("Finish loading zone into zonemgr.")
-
def _zone_is_expired(self, zone_name_class):
"""Judge whether a zone is expired or not."""
zone_expired_time = float(self._get_zone_soa_rdata(zone_name_class).split(" ")[EXPIRED_OFFSET])
@@ -416,6 +402,32 @@ class ZonemgrRefresh:
def update_config_data(self, new_config):
""" update ZonemgrRefresh config """
+ backup = self._zonemgr_refresh_info.copy()
+ try:
+ required = {}
+ # Add new zones
+ for secondary_zone in new_config.get('secondary_zones'):
+ name = secondary_zone['name']
+ # Be tolerant to sclerotic users who forget the final dot
+ if name[-1] != '.':
+ name = name + '.'
+ name_class = (name, secondary_zone['class'])
+ required[name_class] = True
+ # Add it only if it isn't there already
+ if not name_class in self._zonemgr_refresh_info:
+ self.zonemgr_add_zone(name_class)
+ # Drop the zones that are no longer there
+ # Do it in two phases, python doesn't like deleting while iterating
+ to_drop = []
+ for old_zone in self._zonemgr_refresh_info:
+ if not old_zone in required:
+ to_drop.append(old_zone)
+ for drop in to_drop:
+ del self._zonemgr_refresh_info[drop]
+ # If we are not able to find it in database, restore the original
+ except:
+ self._zonemgr_refresh_info = backup
+ raise
self._lowerbound_refresh = new_config.get('lowerbound_refresh')
self._lowerbound_retry = new_config.get('lowerbound_retry')
self._max_transfer_timeout = new_config.get('max_transfer_timeout')
@@ -472,26 +484,37 @@ class Zonemgr:
def config_handler(self, new_config):
""" Update config data. """
answer = create_answer(0)
+ ok = True
+ complete = self._config_data.copy()
for key in new_config:
- if key not in self._config_data:
+ if key not in complete:
answer = create_answer(1, "Unknown config data: " + str(key))
+ ok = False
continue
- self._config_data[key] = new_config[key]
+ complete[key] = new_config[key]
- self._config_data_check(self._config_data)
- if (self._zone_refresh):
- self._zone_refresh.update_config_data(self._config_data)
+ self._config_data_check(complete)
+ if self._zone_refresh is not None:
+ try:
+ self._zone_refresh.update_config_data(complete)
+ except Exception as e:
+ answer = create_answer(1, str(e))
+ ok = False
+ if ok:
+ self._config_data = complete
return answer
def _config_data_check(self, config_data):
- """Check whether the new config data is valid or
- not. """
+ """Check whether the new config data is valid or
+ not. It contains only basic logic, not full check against
+ database."""
# jitter should not be bigger than half of the original value
if config_data.get('jitter_scope') > 0.5:
config_data['jitter_scope'] = 0.5
log_msg("[b10-zonemgr] jitter_scope is too big, its value will "
- "be set to 0.5")
+ "be set to 0.5")
+
def _parse_cmd_params(self, args, command):
zone_name = args.get("zone_name")
diff --git a/src/bin/zonemgr/zonemgr.spec.pre.in b/src/bin/zonemgr/zonemgr.spec.pre.in
index 0539ef3..9df01f2 100644
--- a/src/bin/zonemgr/zonemgr.spec.pre.in
+++ b/src/bin/zonemgr/zonemgr.spec.pre.in
@@ -25,6 +25,32 @@
"item_type": "real",
"item_optional": false,
"item_default": 0.25
+ },
+ {
+ "item_name": "secondary_zones",
+ "item_type": "list",
+ "item_optional": false,
+ "item_default": [],
+ "list_item_spec": {
+ "item_name": "secondary_zone",
+ "item_type": "map",
+ "item_optional": false,
+ "item_default": {},
+ "map_item_spec": [
+ {
+ "item_name": "class",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": "IN"
+ },
+ {
+ "item_name": "name",
+ "item_type": "string",
+ "item_optional": false,
+ "item_default": ""
+ }
+ ]
+ }
}
],
"commands": [
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
index 27d9b8b..a5f67e5 100644
--- a/src/lib/Makefile.am
+++ b/src/lib/Makefile.am
@@ -1,2 +1,2 @@
-SUBDIRS = exceptions dns cc config datasrc python xfr bench log \
- resolve nsas cache asiolink testutils server_common
+SUBDIRS = exceptions dns cc config util python xfr bench log asiolink \
+ nsas cache resolve testutils datasrc server_common
diff --git a/src/lib/asiolink/Makefile.am b/src/lib/asiolink/Makefile.am
index b0ce027..2fda728 100644
--- a/src/lib/asiolink/Makefile.am
+++ b/src/lib/asiolink/Makefile.am
@@ -13,41 +13,39 @@ CLEANFILES = *.gcno *.gcda
# which would make the build fail with -Werror (our default setting).
lib_LTLIBRARIES = libasiolink.la
libasiolink_la_SOURCES = asiolink.h
+libasiolink_la_SOURCES += asiolink_utilities.h
+libasiolink_la_SOURCES += asiodef.cc asiodef.h
libasiolink_la_SOURCES += dns_answer.h
libasiolink_la_SOURCES += dns_lookup.h
libasiolink_la_SOURCES += dns_server.h
-libasiolink_la_SOURCES += dns_service.h dns_service.cc
+libasiolink_la_SOURCES += dns_service.cc dns_service.h
libasiolink_la_SOURCES += dummy_io_cb.h
-libasiolink_la_SOURCES += interval_timer.h interval_timer.cc
-libasiolink_la_SOURCES += io_address.h io_address.cc
+libasiolink_la_SOURCES += interval_timer.cc interval_timer.h
+libasiolink_la_SOURCES += io_address.cc io_address.h
libasiolink_la_SOURCES += io_asio_socket.h
-libasiolink_la_SOURCES += io_endpoint.h io_endpoint.cc
+libasiolink_la_SOURCES += io_endpoint.cc io_endpoint.h
libasiolink_la_SOURCES += io_error.h
-libasiolink_la_SOURCES += io_fetch.h io_fetch.cc
+libasiolink_la_SOURCES += io_fetch.cc io_fetch.h
libasiolink_la_SOURCES += io_message.h
+libasiolink_la_SOURCES += qid_gen.cc qid_gen.h
libasiolink_la_SOURCES += io_service.h io_service.cc
libasiolink_la_SOURCES += io_socket.h io_socket.cc
-libasiolink_la_SOURCES += recursive_query.h recursive_query.cc
libasiolink_la_SOURCES += simple_callback.h
libasiolink_la_SOURCES += tcp_endpoint.h
-libasiolink_la_SOURCES += tcp_server.h tcp_server.cc
+libasiolink_la_SOURCES += tcp_server.cc tcp_server.h
libasiolink_la_SOURCES += tcp_socket.h
libasiolink_la_SOURCES += udp_endpoint.h
-libasiolink_la_SOURCES += udp_server.h udp_server.cc
+libasiolink_la_SOURCES += udp_server.cc udp_server.h
libasiolink_la_SOURCES += udp_socket.h
-libasiolink_la_SOURCES += qid_gen.cc qid_gen.h
+
+EXTRA_DIST = asiodef.msg
+
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
libasiolink_la_CXXFLAGS = $(AM_CXXFLAGS)
-if USE_GXX
-libasiolink_la_CXXFLAGS += -Wno-unused-parameter
-endif
if USE_CLANGPP
# Same for clang++, but we need to turn off -Werror completely.
libasiolink_la_CXXFLAGS += -Wno-error
endif
libasiolink_la_CPPFLAGS = $(AM_CPPFLAGS)
libasiolink_la_LIBADD = $(top_builddir)/src/lib/log/liblog.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/resolve/libresolve.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/cache/libcache.la
-libasiolink_la_LIBADD += $(top_builddir)/src/lib/nsas/libnsas.la
diff --git a/src/lib/asiolink/asiodef.cc b/src/lib/asiolink/asiodef.cc
new file mode 100644
index 0000000..94c71b5
--- /dev/null
+++ b/src/lib/asiolink/asiodef.cc
@@ -0,0 +1,37 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 2011
+
+#include <cstddef>
+#include <log/message_types.h>
+#include <log/message_initializer.h>
+
+namespace asiolink {
+
+extern const isc::log::MessageID ASIO_FETCHCOMP = "FETCHCOMP";
+extern const isc::log::MessageID ASIO_FETCHSTOP = "FETCHSTOP";
+extern const isc::log::MessageID ASIO_OPENSOCK = "OPENSOCK";
+extern const isc::log::MessageID ASIO_RECVSOCK = "RECVSOCK";
+extern const isc::log::MessageID ASIO_RECVTMO = "RECVTMO";
+extern const isc::log::MessageID ASIO_SENDSOCK = "SENDSOCK";
+extern const isc::log::MessageID ASIO_UNKORIGIN = "UNKORIGIN";
+extern const isc::log::MessageID ASIO_UNKRESULT = "UNKRESULT";
+
+} // namespace asiolink
+
+namespace {
+
+const char* values[] = {
+ "FETCHCOMP", "upstream fetch to %s(%d) has now completed",
+ "FETCHSTOP", "upstream fetch to %s(%d) has been stopped",
+ "OPENSOCK", "error %d opening %s socket to %s(%d)",
+ "RECVSOCK", "error %d reading %s data from %s(%d)",
+ "RECVTMO", "receive timeout while waiting for data from %s(%d)",
+ "SENDSOCK", "error %d sending data using %s to %s(%d)",
+ "UNKORIGIN", "unknown origin for ASIO error code %d (protocol: %s, address %s)",
+ "UNKRESULT", "unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d)",
+ NULL
+};
+
+const isc::log::MessageInitializer initializer(values);
+
+} // Anonymous namespace
+
diff --git a/src/lib/asiolink/asiodef.h b/src/lib/asiolink/asiodef.h
new file mode 100644
index 0000000..ba77817
--- /dev/null
+++ b/src/lib/asiolink/asiodef.h
@@ -0,0 +1,21 @@
+// File created from asiodef.msg on Mon Feb 28 17:15:30 2011
+
+#ifndef __ASIODEF_H
+#define __ASIODEF_H
+
+#include <log/message_types.h>
+
+namespace asiolink {
+
+extern const isc::log::MessageID ASIO_FETCHCOMP;
+extern const isc::log::MessageID ASIO_FETCHSTOP;
+extern const isc::log::MessageID ASIO_OPENSOCK;
+extern const isc::log::MessageID ASIO_RECVSOCK;
+extern const isc::log::MessageID ASIO_RECVTMO;
+extern const isc::log::MessageID ASIO_SENDSOCK;
+extern const isc::log::MessageID ASIO_UNKORIGIN;
+extern const isc::log::MessageID ASIO_UNKRESULT;
+
+} // namespace asiolink
+
+#endif // __ASIODEF_H
diff --git a/src/lib/asiolink/asiodef.msg b/src/lib/asiolink/asiodef.msg
new file mode 100644
index 0000000..2fcadd1
--- /dev/null
+++ b/src/lib/asiolink/asiodef.msg
@@ -0,0 +1,56 @@
+# 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.
+
+$PREFIX ASIO_
+$NAMESPACE asiolink
+
+FETCHCOMP upstream fetch to %s(%d) has now completed
++ A debug message, this records the the upstream fetch (a query made by the
++ resolver on behalf of its client) to the specified address has completed.
+
+FETCHSTOP upstream fetch to %s(%d) has been stopped
++ An external component has requested the halting of an upstream fetch. This
++ is an allowed operation, and the message should only appear if debug is
++ enabled.
+
+OPENSOCK error %d opening %s socket to %s(%d)
++ The asynchronous I/O code encountered an error when trying to open a socket
++ of the specified protocol in order to send a message to the target address.
++ The the number of the system error that cause the problem is given in the
++ message.
+
+RECVSOCK error %d reading %s data from %s(%d)
++ The asynchronous I/O code encountered an error when trying read data from
++ the specified address on the given protocol. The the number of the system
++ error that cause the problem is given in the message.
+
+SENDSOCK error %d sending data using %s to %s(%d)
++ The asynchronous I/O code encountered an error when trying send data to
++ the specified address on the given protocol. The the number of the system
++ error that cause the problem is given in the message.
+
+RECVTMO receive timeout while waiting for data from %s(%d)
++ An upstream fetch from the specified address timed out. This may happen for
++ any number of reasons and is most probably a problem at the remote server
++ or a problem on the network. The message will only appear if debug is
++ enabled.
+
+UNKORIGIN unknown origin for ASIO error code %d (protocol: %s, address %s)
++ This message should not appear and indicates an internal error if it does.
++ Please enter a bug report.
+
+UNKRESULT unknown result (%d) when IOFetch::stop() was executed for I/O to %s(%d)
++ The termination method of the resolver's upstream fetch class was called with
++ an unknown result code (which is given in the message). This message should
++ not appear and may indicate an internal error. Please enter a bug report.
diff --git a/src/lib/asiolink/asiolink.h b/src/lib/asiolink/asiolink.h
index 03951ae..6e8fe84 100644
--- a/src/lib/asiolink/asiolink.h
+++ b/src/lib/asiolink/asiolink.h
@@ -25,7 +25,6 @@
#include <asiolink/dns_lookup.h>
#include <asiolink/dns_answer.h>
#include <asiolink/simple_callback.h>
-#include <asiolink/recursive_query.h>
#include <asiolink/interval_timer.h>
#include <asiolink/io_address.h>
@@ -85,7 +84,3 @@
/// http://think-async.com/Asio/asio-1.3.1/doc/asio/reference/asio_handler_allocate.html
#endif // __ASIOLINK_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/asiolink_utilities.h b/src/lib/asiolink/asiolink_utilities.h
new file mode 100644
index 0000000..659e6a0
--- /dev/null
+++ b/src/lib/asiolink/asiolink_utilities.h
@@ -0,0 +1,61 @@
+// 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 __ASIOLINK_UTILITIES_H
+#define __ASIOLINK_UTILITIES_H
+
+#include <cstddef>
+
+namespace asiolink {
+
+/// \brief Read Unsigned 16-Bit Integer from Buffer
+///
+/// This is essentially a copy of the isc::dns::InputBuffer::readUint16. It
+/// should really be moved into a separate library.
+///
+/// \param buffer Data buffer at least two bytes long of which the first two
+/// bytes are assumed to represent a 16-bit integer in network-byte
+/// order.
+///
+/// \return Value of 16-bit integer
+inline uint16_t
+readUint16(const void* buffer) {
+ const uint8_t* byte_buffer = static_cast<const uint8_t*>(buffer);
+
+ uint16_t result = (static_cast<uint16_t>(byte_buffer[0])) << 8;
+ result |= static_cast<uint16_t>(byte_buffer[1]);
+
+ return (result);
+}
+
+/// \brief Write Unisgned 16-Bit Integer to Buffer
+///
+/// This is essentially a copy of isc::dns::OutputBuffer::writeUint16. It
+/// should really be moved into a separate library.
+///
+/// \param value 16-bit value to convert
+/// \param buffer Data buffer at least two bytes long into which the 16-bit
+/// value is written in network-byte order.
+
+inline void
+writeUint16(uint16_t value, void* buffer) {
+ uint8_t* byte_buffer = static_cast<uint8_t*>(buffer);
+
+ byte_buffer[0] = static_cast<uint8_t>((value & 0xff00U) >> 8);
+ byte_buffer[1] = static_cast<uint8_t>(value & 0x00ffU);
+}
+
+} // namespace asiolink
+
+#endif // __ASIOLINK_UTILITIES_H
diff --git a/src/lib/asiolink/dns_lookup.h b/src/lib/asiolink/dns_lookup.h
index 0788853..a79976f 100644
--- a/src/lib/asiolink/dns_lookup.h
+++ b/src/lib/asiolink/dns_lookup.h
@@ -63,8 +63,10 @@ public:
///
/// \param io_message The event message to handle
/// \param message The DNS MessagePtr that needs handling
+ /// \param answer_message The final answer will be constructed in
+ /// this MessagePtr
/// \param buffer The final answer is put here
- /// \param DNSServer DNSServer object to use
+ /// \param server DNSServer object to use
virtual void operator()(const IOMessage& io_message,
isc::dns::MessagePtr message,
isc::dns::MessagePtr answer_message,
diff --git a/src/lib/asiolink/dns_server.h b/src/lib/asiolink/dns_server.h
index 352ea8e..f15f808 100644
--- a/src/lib/asiolink/dns_server.h
+++ b/src/lib/asiolink/dns_server.h
@@ -21,7 +21,7 @@ namespace asiolink {
/// \brief The \c DNSServer class is a wrapper (and base class) for
/// classes which provide DNS server functionality.
-///
+///
/// The classes derived from this one, \c TCPServer and \c UDPServer,
/// act as the interface layer between clients sending queries, and
/// functions defined elsewhere that provide answers to those queries.
@@ -42,10 +42,10 @@ namespace asiolink {
/// when "forking", and that instances will be posted as ASIO handler
/// objects, which are always copied.
///
-/// Because these objects are frequently copied, it is recommended
+/// Because these objects are frequently copied, it is recommended
/// that derived classes be kept small to reduce copy overhead.
class DNSServer {
-protected:
+protected:
///
/// \name Constructors and destructors
///
@@ -66,7 +66,7 @@ public:
/// the ones in the derived class. This makes it possible to pass
/// instances of derived classes as references to this base class
/// without losing access to derived class data.
- ///
+ ///
//@{
/// \brief The funtion operator
virtual void operator()(asio::error_code ec = asio::error_code(),
@@ -87,7 +87,7 @@ public:
/// \brief Indicate whether the server is able to send an answer
/// to a query.
- ///
+ ///
/// This is presently used only for testing purposes.
virtual bool hasAnswer() { return (self_->hasAnswer()); }
diff --git a/src/lib/asiolink/dns_service.h b/src/lib/asiolink/dns_service.h
index 84aa5fb..9a3fb4c 100644
--- a/src/lib/asiolink/dns_service.h
+++ b/src/lib/asiolink/dns_service.h
@@ -26,13 +26,13 @@ class DNSLookup;
class DNSAnswer;
class DNSServiceImpl;
+/// \brief Handle DNS Queries
///
/// DNSService is the service that handles DNS queries and answers with
/// a given IOService. This class is mainly intended to hold all the
/// logic that is shared between the authoritative and the recursive
/// server implementations. As such, it handles asio, including config
/// updates (through the 'Checkinprovider'), and listening sockets.
-///
class DNSService {
///
/// \name Constructors and Destructor
@@ -66,8 +66,8 @@ public:
///
/// \param io_service The IOService to work with
/// \param port the port to listen on
- /// \param ipv4 If true, listen on ipv4 'any'
- /// \param ipv6 If true, listen on ipv6 'any'
+ /// \param use_ipv4 If true, listen on ipv4 'any'
+ /// \param use_ipv6 If true, listen on ipv6 'any'
/// \param checkin Provider for cc-channel events (see \c SimpleCallback)
/// \param lookup The lookup provider (see \c DNSLookup)
/// \param answer The answer provider (see \c DNSAnswer)
diff --git a/src/lib/asiolink/dummy_io_cb.h b/src/lib/asiolink/dummy_io_cb.h
index bde656c..0006b95 100644
--- a/src/lib/asiolink/dummy_io_cb.h
+++ b/src/lib/asiolink/dummy_io_cb.h
@@ -39,6 +39,14 @@ public:
/// \brief Asynchronous I/O callback method
///
/// \param error Unused
+ void operator()(asio::error_code)
+ {
+ // TODO: log an error if this method ever gets called.
+ }
+
+ /// \brief Asynchronous I/O callback method
+ ///
+ /// \param error Unused
/// \param length Unused
void operator()(asio::error_code, size_t)
{
diff --git a/src/lib/asiolink/internal/iofetch.h b/src/lib/asiolink/internal/iofetch.h
deleted file mode 100644
index d066c92..0000000
--- a/src/lib/asiolink/internal/iofetch.h
+++ /dev/null
@@ -1,125 +0,0 @@
-// Copyright (C) 2010 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 __IOFETCH_H
-#define __IOFETCH_H 1
-
-#include <config.h>
-
-#include <asio.hpp>
-#include <boost/shared_array.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-#include <dns/messagerenderer.h>
-
-#include <asiolink/asiolink.h>
-#include <asiolink/internal/coroutine.h>
-
-// This file contains TCP/UDP-specific implementations of generic classes
-// defined in asiolink.h. It is *not* intended to be part of the public
-// API.
-
-namespace asiolink {
-//
-// Asynchronous UDP/TCP coroutine for upstream fetches
-//
-//class IOFetch : public coroutine, public UdpFetch, public TcpFetch {
-class IOFetch : public coroutine {
-public:
- // TODO Maybe this should be more generic than just for IOFetch?
- ///
- /// \brief Result of the query
- ///
- /// This is related only to contacting the remote server. If the answer
- ///indicates error, it is still counted as SUCCESS here, if it comes back.
- ///
- enum Result {
- SUCCESS,
- TIME_OUT,
- STOPPED
- };
- /// Abstract callback for the IOFetch.
- class Callback {
- public:
- virtual ~Callback() {}
-
- /// This will be called when the IOFetch is completed
- virtual void operator()(Result result) = 0;
- };
- ///
- /// \brief Constructor.
- ///
- /// It creates the query.
- /// @param callback will be called when we terminate. It is your task to
- /// delete it if allocated on heap.
- ///@param timeout in ms.
- ///
- IOFetch(asio::io_service& io_service,
- const isc::dns::Question& q,
- const IOAddress& addr, uint16_t port,
- isc::dns::OutputBufferPtr buffer,
- Callback* callback, int timeout = -1,
- int protocol = IPPROTO_UDP);
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
- /// Terminate the query.
- void stop(Result reason = STOPPED);
-private:
- enum { MAX_LENGTH = 4096 };
-
- ///
- /// \short Private data
- ///
- /// They are not private because of stability of the
- /// interface (this is private class anyway), but because this class
- /// will be copyed often (it is used as a coroutine and passed as callback
- /// to many async_*() functions) and we want keep the same data. Some of
- /// the data is not copyable too.
- ///
- //struct IOFetchProtocol;
- //boost::shared_ptr<IOFetchProtocol> data_;
- //struct UdpData;
- //struct TcpData;
- boost::shared_ptr<UdpFetch> data_;
- boost::shared_ptr<TcpFetch> tcp_data_;
-};
-class UdpFetch : public IOFetch {
- public:
- struct UdpData;
- explicit UdpFetch(asio::io_service& io_service,
- const isc::dns::Question& q,
- const IOAddress& addr,
- uint16_t port,
- isc::dns::OutputBufferPtr buffer,
- IOFetch::Callback *callback,
- int timeout);
-};
-class TcpFetch : public IOFetch {
- public:
- struct TcpData;
- explicit TcpFetch(io_service& io_service, const Question& q,
- const IOAddress& addr, uint16_t port,
- OutputBufferPtr buffer, Callback *callback, int timeout);
-};
-
-}
-
-
-#endif // __IOFETCH_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/interval_timer.h b/src/lib/asiolink/interval_timer.h
index d805cd7..6c43327 100644
--- a/src/lib/asiolink/interval_timer.h
+++ b/src/lib/asiolink/interval_timer.h
@@ -37,7 +37,7 @@ struct IntervalTimerImpl;
/// The function calls the call back function set by \c setup() and updates
/// the timer to expire in (now + interval) milliseconds.
/// The type of call back function is \c void(void).
-///
+///
/// The call back function will not be called if the instance of this class is
/// destroyed before the timer is expired.
///
diff --git a/src/lib/asiolink/io_address.h b/src/lib/asiolink/io_address.h
index 0d2787f..53c1a7a 100644
--- a/src/lib/asiolink/io_address.h
+++ b/src/lib/asiolink/io_address.h
@@ -61,7 +61,7 @@ public:
/// This constructor never throws an exception.
///
/// \param asio_address The ASIO \c ip::address to be converted.
- IOAddress(const asio::ip::address& asio_adress);
+ IOAddress(const asio::ip::address& asio_address);
//@}
/// \brief Convert the address to a string.
@@ -121,7 +121,3 @@ private:
} // asiolink
#endif // __IO_ADDRESS_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/io_asio_socket.h b/src/lib/asiolink/io_asio_socket.h
index eae9b32..ac793a6 100644
--- a/src/lib/asiolink/io_asio_socket.h
+++ b/src/lib/asiolink/io_asio_socket.h
@@ -26,6 +26,8 @@
#include <exceptions/exceptions.h>
#include <coroutine.h>
+#include <dns/buffer.h>
+
#include <asiolink/io_error.h>
#include <asiolink/io_socket.h>
@@ -41,7 +43,24 @@ public:
IOError(file, line, what) {}
};
+/// \brief Error setting socket options
+///
+/// Thrown if attempt to change socket options fails.
+class SocketSetError : public IOError {
+public:
+ SocketSetError(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+/// \brief Buffer overflow
+///
+/// Thrown if an attempt is made to receive into an area beyond the end of
+/// the receive data buffer.
+class BufferOverflow : public IOError {
+public:
+ BufferOverflow(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
/// Forward declaration of an IOEndpoint
class IOEndpoint;
@@ -91,24 +110,23 @@ public:
/// \brief Return the "native" representation of the socket.
///
- /// In practice, this is the file descriptor of the socket for
- /// UNIX-like systems so the current implementation simply uses
- /// \c int as the type of the return value.
- /// We may have to need revisit this decision later.
+ /// In practice, this is the file descriptor of the socket for UNIX-like
+ /// systems so the current implementation simply uses \c int as the type of
+ /// the return value. We may have to need revisit this decision later.
///
- /// In general, the application should avoid using this method;
- /// it essentially discloses an implementation specific "handle" that
- /// can change the internal state of the socket (consider the
- /// application closes it, for example).
- /// But we sometimes need to perform very low-level operations that
- /// requires the native representation. Passing the file descriptor
- /// to a different process is one example.
- /// This method is provided as a necessary evil for such limited purposes.
+ /// In general, the application should avoid using this method; it
+ /// essentially discloses an implementation specific "handle" that can
+ /// change the internal state of the socket (consider what would happen if
+ /// the application closes it, for example). But we sometimes need to
+ /// perform very low-level operations that requires the native
+ /// representation. Passing the file descriptor to a different process is
+ /// one example. This method is provided as a necessary evil for such
+ /// limited purposes.
///
/// This method never throws an exception.
///
/// \return The native representation of the socket. This is the socket
- /// file descriptor for UNIX-like systems.
+ /// file descriptor for UNIX-like systems.
virtual int getNative() const = 0;
/// \brief Return the transport protocol of the socket.
@@ -118,36 +136,50 @@ public:
///
/// This method never throws an exception.
///
- /// \return IPPROTO_UDP for UDP sockets
- /// \return IPPROTO_TCP for TCP sockets
+ /// \return \c IPPROTO_UDP for UDP sockets, \c IPPROTO_TCP for TCP sockets
virtual int getProtocol() const = 0;
- /// \brief Open AsioSocket
+ /// \brief Is Open() synchronous?
///
- /// Opens the socket for asynchronous I/O. On a UDP socket, this is merely
- /// an "open()" on the underlying socket (so completes immediately), but on
- /// a TCP socket it also connects to the remote end (which is done as an
- /// asynchronous operation).
+ /// On a TCP socket, an "open" operation is a call to the socket's "open()"
+ /// method followed by a connection to the remote system: it is an
+ /// asynchronous operation. On a UDP socket, it is just a call to "open()"
+ /// and completes synchronously.
///
/// For TCP, signalling of the completion of the operation is done by
/// by calling the callback function in the normal way. This could be done
/// for UDP (by posting en event on the event queue); however, that will
- /// incur additional overhead in the most common case. Instead, the return
- /// value indicates whether the operation was asynchronous or not. If yes,
- /// (i.e. TCP) the callback has been posted to the event queue: if no (UDP),
- /// no callback has been posted (in which case it is up to the caller as to
- /// whether they want to manually post the callback themself.)
+ /// incur additional overhead in the most common case. So we give the
+ /// caller the choice for calling this open() method synchronously or
+ /// asynchronously.
+ ///
+ /// Owing to the way that the stackless coroutines are implemented, we need
+ /// to know _before_ executing the "open" function whether or not it is
+ /// asynchronous. So this method is called to provide that information.
+ ///
+ /// (The reason there is a need to know is because the call to open() passes
+ /// in the state of the coroutine at the time the call is made. On an
+ /// asynchronous I/O, we need to set the state to point to the statement
+ /// after the call to open() _before_ we pass the corouine to the open()
+ /// call. Unfortunately, the macros that set the state of the coroutine
+ /// also yield control - which we don't want to do if the open is
+ /// synchronous. Hence we need to know before we make the call to open()
+ /// whether that call will complete asynchronously.)
+ virtual bool isOpenSynchronous() const = 0;
+
+ /// \brief Open AsioSocket
+ ///
+ /// Opens the socket for asynchronous I/O. The open will complete
+ /// synchronously on UCP or asynchronously on TCP (in which case a callback
+ /// will be queued).
///
/// \param endpoint Pointer to the endpoint object. This is ignored for
- /// a UDP socket (the target is specified in the send call), but should
- /// be of type TCPEndpoint for a TCP connection.
+ /// a UDP socket (the target is specified in the send call), but
+ /// should be of type TCPEndpoint for a TCP connection.
/// \param callback I/O Completion callback, called when the operation has
- /// completed, but only if the operation was asynchronous.
- ///
- /// \return true if an asynchronous operation was started and the caller
- /// should yield and wait for completion, false if the operation was
- /// completed synchronously and no callback was queued.
- virtual bool open(const IOEndpoint* endpoint, C& callback) = 0;
+ /// completed, but only if the operation was asynchronous. (It is
+ /// ignored on a UDP socket.)
+ virtual void open(const IOEndpoint* endpoint, C& callback) = 0;
/// \brief Send Asynchronously
///
@@ -160,44 +192,85 @@ public:
/// \param endpoint Target of the send
/// \param callback Callback object.
virtual void asyncSend(const void* data, size_t length,
- const IOEndpoint* endpoint, C& callback) = 0;
+ const IOEndpoint* endpoint, C& callback) = 0;
/// \brief Receive Asynchronously
///
- /// This correstponds to async_receive_from() for UDP sockets and
+ /// This corresponds to async_receive_from() for UDP sockets and
/// async_receive() for TCP. In both cases, an endpoint argument is
/// supplied to receive the source of the communication. For TCP it will
/// be filled in with details of the connection.
///
/// \param data Buffer to receive incoming message
/// \param length Length of the data buffer
- /// \param cumulative Amount of data that should already be in the buffer.
+ /// \param offset Offset into buffer where data is to be put. Although the
+ /// offset could be implied by adjusting "data" and "length"
+ /// appropriately, using this argument allows data to be specified as
+ /// "const void*" - the overhead of converting it to a pointer to a
+ /// set of bytes is hidden away here.
/// \param endpoint Source of the communication
/// \param callback Callback object
- virtual void asyncReceive(void* data, size_t length, size_t cumulative,
- IOEndpoint* endpoint, C& callback) = 0;
-
- /// \brief Checks if the data received is complete.
- ///
- /// This applies to TCP receives, where the data is a byte stream and a
- /// receive is not guaranteed to receive the entire message. DNS messages
- /// over TCP are prefixed by a two-byte count field. This method takes the
- /// amount received so far and the amount received in this I/O and checks
- /// if the message is complete, returning the appropriate indication. As
- /// a side-effect, it also updates the amount received.
- ///
- /// For a UDP receive, all the data is received in one I/O, so this is
- /// effectively a no-op (although it does update the amount received).
- ///
- /// \param data Data buffer containing data to date
- /// \param length Amount of data received in last asynchronous I/O
- /// \param cumulative On input, amount of data received before the last
- /// I/O. On output, the total amount of data received to date.
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback) = 0;
+
+ /// \brief Processes received data
+ ///
+ /// In the IOFetch code, data is received into a staging buffer before being
+ /// copied into the target buffer. (This is because (a) we don't know how
+ /// much data we will be receiving, so don't know how to size the output
+ /// buffer and (b) TCP data is preceded by a two-byte count field that needs
+ /// to be discarded before being returned to the user.)
+ ///
+ /// An additional consideration is that TCP data is not received in one
+ /// I/O - it may take a number of I/Os - each receiving any non-zero number
+ /// of bytes - to read the entire message.
+ ///
+ /// So the IOFetch code has to loop until it determines that all the data
+ /// has been read. This is where this method comes in. It has several
+ /// functions:
+ ///
+ /// - It checks if the received data is complete.
+ /// - If data is not complete, decides if the next set of data is to go into
+ /// the start of the staging buffer or at some offset into it. (This
+ /// simplifies the case we could have in a TCP receive where the two-byte
+ /// count field is received in one-byte chunks: we put off interpreting
+ /// the count until we have all of it. The alternative - copying the
+ /// data to the output buffer and interpreting the count from there -
+ /// would require moving the data in the output buffer by two bytes before
+ /// returning it to the caller.)
+ /// - Copies data from the staging buffer into the output buffer.
+ ///
+ /// This functionality mainly applies to TCP receives. For UDP, all the
+ /// data is received in one I/O, so this just copies the data into the
+ /// output buffer.
+ ///
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed (this includes the TCP count field if appropriate).
+ /// The value should be set to zero before the receive loop is
+ /// entered, and it will be updated by this method as required.
+ /// \param offset Offset into the staging buffer where the next read should
+ /// put the received data. It should be set to zero before the first
+ /// call and may be updated by this method.
+ /// \param expected Expected amount of data to be received. This is
+ /// really the TCP count field and is set to that value when enough
+ /// of a TCP message is received. It should be initialized to -1
+ /// before the first read is executed.
+ /// \param outbuff Output buffer. Data in the staging buffer may be copied
+ /// to this output buffer in the call.
///
/// \return true if the receive is complete, false if another receive is
- /// needed.
- virtual bool receiveComplete(void* data, size_t length,
- size_t& cumulative) = 0;
+ /// needed. This is always true for UDP, but for TCP involves
+ /// checking the amount of data received so far against the amount
+ /// expected (as indicated by the two-byte count field). If this
+ /// method returns false, another read should be queued and data
+ /// should be read into the staging buffer at offset given by the
+ /// "offset" parameter.
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff) = 0;
/// \brief Cancel I/O On AsioSocket
virtual void cancel() = 0;
@@ -244,6 +317,13 @@ public:
virtual int getProtocol() const { return (protocol_); }
+ /// \brief Is socket opening synchronous?
+ ///
+ /// \return true - it is for this class.
+ bool isOpenSynchronous() const {
+ return true;
+ }
+
/// \brief Open AsioSocket
///
/// A call that is a no-op on UDP sockets, this opens a connection to the
@@ -273,21 +353,31 @@ public:
///
/// \param data Unused
/// \param length Unused
- /// \param cumulative Unused
+ /// \param offset Unused
/// \param endpoint Unused
/// \param callback Unused
- virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) { }
+ virtual void asyncReceive(void* data, size_t, size_t, IOEndpoint*, C&) {
+ }
+
/// \brief Checks if the data received is complete.
///
- /// \param data Unused
+ /// \param staging Unused
/// \param length Unused
/// \param cumulative Unused
+ /// \param offset Unused.
+ /// \param expected Unused.
+ /// \param outbuff Unused.
///
/// \return Always true
- virtual bool receiveComplete(void*, size_t, size_t&) {
+ virtual bool receiveComplete(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+ {
return (true);
}
+
/// \brief Cancel I/O On AsioSocket
///
/// Must be supplied as it is abstract in the base class.
diff --git a/src/lib/asiolink/io_endpoint.cc b/src/lib/asiolink/io_endpoint.cc
index bf79f61..e0b1a9e 100644
--- a/src/lib/asiolink/io_endpoint.cc
+++ b/src/lib/asiolink/io_endpoint.cc
@@ -22,6 +22,7 @@
#include <asiolink/io_address.h>
#include <asiolink/io_error.h>
+#include <asiolink/io_endpoint.h>
#include <asiolink/tcp_endpoint.h>
#include <asiolink/udp_endpoint.h>
@@ -43,4 +44,17 @@ IOEndpoint::create(const int protocol, const IOAddress& address,
protocol);
}
+bool
+IOEndpoint::operator==(const IOEndpoint& other) const {
+ return (getProtocol() == other.getProtocol() &&
+ getPort() == other.getPort() &&
+ getFamily() == other.getFamily() &&
+ getAddress() == other.getAddress());
+}
+
+bool
+IOEndpoint::operator!=(const IOEndpoint& other) const {
+ return (!operator==(other));
+}
+
}
diff --git a/src/lib/asiolink/io_endpoint.h b/src/lib/asiolink/io_endpoint.h
index 62b9e47..d21da96 100644
--- a/src/lib/asiolink/io_endpoint.h
+++ b/src/lib/asiolink/io_endpoint.h
@@ -89,6 +89,9 @@ public:
/// \brief Returns the address family of the endpoint.
virtual short getFamily() const = 0;
+ bool operator==(const IOEndpoint& other) const;
+ bool operator!=(const IOEndpoint& other) const;
+
/// \brief A polymorphic factory of endpoint from address and port.
///
/// This method creates a new instance of (a derived class of)
@@ -116,7 +119,3 @@ public:
} // asiolink
#endif // __IO_ENDPOINT_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/io_fetch.cc b/src/lib/asiolink/io_fetch.cc
index d670408..4748144 100644
--- a/src/lib/asiolink/io_fetch.cc
+++ b/src/lib/asiolink/io_fetch.cc
@@ -20,49 +20,150 @@
#include <netinet/in.h>
#include <boost/bind.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <dns/messagerenderer.h>
#include <dns/opcode.h>
#include <dns/rcode.h>
-#include <log/dummylog.h>
+#include <log/logger.h>
#include <asio.hpp>
+#include <asio/deadline_timer.hpp>
+
+#include <asiolink/asiodef.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_asio_socket.h>
+#include <asiolink/io_endpoint.h>
#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+#include <asiolink/udp_endpoint.h>
+#include <asiolink/udp_socket.h>
+#include <asiolink/qid_gen.h>
+
+#include <stdint.h>
using namespace asio;
using namespace isc::dns;
using namespace isc::log;
using namespace std;
-namespace {
- // Temporarily borrowed from buffer.h
- // \brief Read an unsigned 16-bit integer in network byte order from the
- // buffer, convert it to host byte order, and return it.
- //
- // If the remaining length of the buffer is smaller than 16-bit, an
- // exception of class \c isc::dns::InvalidBufferPosition will be thrown.
- uint16_t readUint16(const void* rb, size_t len)
- {
- uint16_t data;
-
- if (sizeof(data) > len) {
- isc_throw(InvalidBufferPosition, "read beyond end of data");
- }
-
- const uint8_t* cp = static_cast<const uint8_t*>(rb);
- data = ((unsigned int)(cp[0])) << 8;
- data |= ((unsigned int)(cp[1]));
+namespace asiolink {
- return (data);
+/// Use the ASIO logger
+
+isc::log::Logger logger("asiolink");
+
+/// \brief IOFetch Data
+///
+/// The data for IOFetch is held in a separate struct pointed to by a shared_ptr
+/// object. This is because the IOFetch object will be copied often (it is used
+/// as a coroutine and passed as callback to many async_*() functions) and we
+/// want keep the same data). Organising the data in this way keeps copying to
+/// a minimum.
+struct IOFetchData {
+
+ // The first two members are shared pointers to a base class because what is
+ // actually instantiated depends on whether the fetch is over UDP or TCP,
+ // which is not known until construction of the IOFetch. Use of a shared
+ // pointer here is merely to ensure deletion when the data object is deleted.
+ boost::scoped_ptr<IOAsioSocket<IOFetch> > socket;
+ ///< Socket to use for I/O
+ boost::scoped_ptr<IOEndpoint> remote_snd;///< Where the fetch is sent
+ boost::scoped_ptr<IOEndpoint> remote_rcv;///< Where the response came from
+ isc::dns::MessagePtr query_message;///< Query message to be sent
+ isc::dns::OutputBufferPtr msgbuf; ///< Wire buffer for question
+ isc::dns::OutputBufferPtr received; ///< Received data put here
+ IOFetch::Callback* callback; ///< Called on I/O Completion
+ asio::deadline_timer timer; ///< Timer to measure timeouts
+ IOFetch::Protocol protocol; ///< Protocol being used
+ size_t cumulative; ///< Cumulative received amount
+ size_t expected; ///< Expected amount of data
+ size_t offset; ///< Offset to receive data
+ bool stopped; ///< Have we stopped running?
+ int timeout; ///< Timeout in ms
+
+ // In case we need to log an error, the origin of the last asynchronous
+ // I/O is recorded. To save time and simplify the code, this is recorded
+ // as the ID of the error message that would be generated if the I/O failed.
+ // This means that we must make sure that all possible "origins" take the
+ // same arguments in their message in the same order.
+ isc::log::MessageID origin; ///< Origin of last asynchronous I/O
+ uint8_t staging[IOFetch::STAGING_LENGTH];
+ ///< Temporary array for received data
+ isc::dns::qid_t qid; ///< The QID set in the query
+
+ /// \brief Constructor
+ ///
+ /// Just fills in the data members of the IOFetchData structure
+ ///
+ /// \param proto Either IOFetch::TCP or IOFetch::UDP.
+ /// \param service I/O Service object to handle the asynchronous
+ /// operations.
+ /// \param query DNS question to send to the upstream server.
+ /// \param address IP address of upstream server
+ /// \param port Port to use for the query
+ /// \param buff Output buffer into which the response (in wire format)
+ /// is written (if a response is received).
+ /// \param cb Callback object containing the callback to be called
+ /// when we terminate. The caller is responsible for managing this
+ /// object and deleting it if necessary.
+ /// \param wait Timeout for the fetch (in ms).
+ ///
+ /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
+ IOFetchData(IOFetch::Protocol proto, IOService& service,
+ isc::dns::MessagePtr query_msg, const IOAddress& address,
+ uint16_t port, isc::dns::OutputBufferPtr& buff, IOFetch::Callback* cb,
+ int wait)
+ :
+ socket((proto == IOFetch::UDP) ?
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new UDPSocket<IOFetch>(service)) :
+ static_cast<IOAsioSocket<IOFetch>*>(
+ new TCPSocket<IOFetch>(service))
+ ),
+ remote_snd((proto == IOFetch::UDP) ?
+ static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+ static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+ ),
+ remote_rcv((proto == IOFetch::UDP) ?
+ static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
+ static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
+ ),
+ query_message(query_msg),
+ msgbuf(new isc::dns::OutputBuffer(512)),
+ received(buff),
+
+ callback(cb),
+ timer(service.get_io_service()),
+ protocol(proto),
+ cumulative(0),
+ expected(0),
+ offset(0),
+ stopped(false),
+ timeout(wait),
+ origin(ASIO_UNKORIGIN),
+ staging(),
+ qid(QidGenerator::getInstance().generateQid())
+ {}
+
+ // Checks if the response we received was ok;
+ // - data contains the buffer we read, as well as the address
+ // we sent to and the address we received from.
+ // length is provided by the operator() in IOFetch.
+ // Addresses must match, number of octets read must be at least
+ // 2, and the first two octets must match the qid of the message
+ // we sent.
+ bool responseOK() {
+ return (*remote_snd == *remote_rcv && cumulative >= 2 &&
+ readUint16(received->getData()) == qid);
}
-
-}
-
-namespace asiolink {
-/// IOFetch Constructor - just initialize the private data
+};
void
-IOFetch::initIOFetch(MessagePtr& query_msg, int protocol, IOService& service,
+IOFetch::initIOFetch(MessagePtr& query_msg, Protocol protocol, IOService& service,
const isc::dns::Question& question,
const IOAddress& address, uint16_t port,
isc::dns::OutputBufferPtr& buff, Callback* cb, int wait)
@@ -72,21 +173,22 @@ IOFetch::initIOFetch(MessagePtr& query_msg, int protocol, IOService& service,
query_msg->setRcode(Rcode::NOERROR());
query_msg->setHeaderFlag(Message::HEADERFLAG_RD);
query_msg->addQuestion(question);
- data_ = boost::shared_ptr<IOFetchData>(new IOFetch::IOFetchData(
+ data_ = boost::shared_ptr<IOFetchData>(new IOFetchData(
protocol, service, query_msg, address,
port, buff, cb, wait));
}
-IOFetch::IOFetch(int protocol, IOService& service,
+/// IOFetch Constructor - just initialize the private data
+IOFetch::IOFetch(Protocol protocol, IOService& service,
const isc::dns::Question& question, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb, int wait)
{
- MessagePtr query_msg(new Message(Message::RENDER));
- initIOFetch(query_msg, protocol, service, question, address, port, buff,
+ MessagePtr msg(new Message(Message::RENDER));
+ initIOFetch(msg, protocol, service, question, address, port, buff,
cb, wait);
}
-IOFetch::IOFetch(int protocol, IOService& service,
+IOFetch::IOFetch(Protocol protocol, IOService& service,
ConstMessagePtr query_message, const IOAddress& address, uint16_t port,
isc::dns::OutputBufferPtr& buff, Callback* cb, int wait)
{
@@ -112,12 +214,23 @@ IOFetch::IOFetch(int protocol, IOService& service,
}
}
+// Return protocol in use.
+
+IOFetch::Protocol
+IOFetch::getProtocol() const {
+ return (data_->protocol);
+}
+
/// The function operator is implemented with the "stackless coroutine"
/// pattern; see internal/coroutine.h for details.
void
-IOFetch::operator()(error_code ec, size_t length) {
- if (ec || data_->stopped) {
+IOFetch::operator()(asio::error_code ec, size_t length) {
+
+ if (data_->stopped) {
+ return;
+ } else if (ec) {
+ logIOFailure(ec);
return;
}
@@ -129,17 +242,10 @@ IOFetch::operator()(error_code ec, size_t length) {
{
MessageRenderer renderer(*data_->msgbuf);
data_->query_message->toWire(renderer);
-
- // As this is a new fetch, clear the amount of data received
- data_->cumulative = 0;
-
- dlog("Sending " + data_->query_message->toText() + " to " +
- data_->remote->getAddress().toText());
}
-
- // If we timeout, we stop, which will shutdown everything and
- // cancel all other attempts to run inside the coroutine
+ // If we timeout, we stop, which will can cancel outstanding I/Os and
+ // shutdown everything.
if (data_->timeout != -1) {
data_->timer.expires_from_now(boost::posix_time::milliseconds(
data_->timeout));
@@ -148,17 +254,21 @@ IOFetch::operator()(error_code ec, size_t length) {
}
// Open a connection to the target system. For speed, if the operation
- // was completed synchronously (i.e. UDP operation) we bypass the yield.
- if (data_->socket->open(data_->remote.get(), *this)) {
- CORO_YIELD;
+ // is synchronous (i.e. UDP operation) we bypass the yield.
+ data_->origin = ASIO_OPENSOCK;
+ if (data_->socket->isOpenSynchronous()) {
+ data_->socket->open(data_->remote_snd.get(), *this);
+ } else {
+ CORO_YIELD data_->socket->open(data_->remote_snd.get(), *this);
}
- while (true) {
+ do {
// Begin an asynchronous send, and then yield. When the send completes,
// we will resume immediately after this point.
+ data_->origin = ASIO_SENDSOCK;
CORO_YIELD data_->socket->asyncSend(data_->msgbuf->getData(),
- data_->msgbuf->getLength(), data_->remote.get(), *this);
-
+ data_->msgbuf->getLength(), data_->remote_snd.get(), *this);
+
// Now receive the response. Since TCP may not receive the entire
// message in one operation, we need to loop until we have received
// it. (This can't be done within the asyncReceive() method because
@@ -166,37 +276,35 @@ IOFetch::operator()(error_code ec, size_t length) {
// we need to yield ... and we *really* don't want to set up another
// coroutine within that method.) So after each receive (and yield),
// we check if the operation is complete and if not, loop to read again.
+ //
+ // Another concession to TCP is that the amount of is contained in the
+ // first two bytes. This leads to two problems:
+ //
+ // a) We don't want those bytes in the return buffer.
+ // b) They may not both arrive in the first I/O.
+ //
+ // So... we need to loop until we have at least two bytes, then store
+ // the expected amount of data. Then we need to loop until we have
+ // received all the data before copying it back to the user's buffer.
+ // And we want to minimise the amount of copying...
+
+ data_->origin = ASIO_RECVSOCK;
+ data_->cumulative = 0; // No data yet received
+ data_->offset = 0; // First data into start of buffer
+ data_->received->clear(); // Clear the receive buffer
do {
- CORO_YIELD data_->socket->asyncReceive(data_->data.get(),
- static_cast<size_t>(MAX_LENGTH), data_->cumulative,
- data_->remote.get(), *this);
- } while (!data_->socket->receiveComplete(data_->data.get(), length,
- data_->cumulative));
-
- // The message is not rendered yet, so we can't print it easily
- dlog("Received response from " + data_->remote->getAddress().toText());
-
- // TODO: move the qid check to wherever we put demuxer (and all
- // DNS related knowledge out of this (as well as the dupe uint16
- // read code)
- // This is just a quick way to check it, if we get the right ID
- // (TODO: from the right address), break the receive-loop
- if (readUint16(data_->data.get(), length) ==
- data_->query_message->getQid()) {
- break;
- } else {
- dlog("Wrong QID in response, waiting for next one");
- }
- }
-
- /// Copy the answer into the response buffer. (TODO: If the
- /// OutputBuffer object were made to meet the requirements of
- /// a MutableBufferSequence, then it could be written to directly
- /// by async_receive_from() and this additional copy step would
- /// be unnecessary.)
- data_->buffer->writeData(data_->data.get(), length);
-
- // Finished with this socket, so close it.
+ CORO_YIELD data_->socket->asyncReceive(data_->staging,
+ static_cast<size_t>(STAGING_LENGTH),
+ data_->offset,
+ data_->remote_rcv.get(), *this);
+ } while (!data_->socket->processReceivedData(data_->staging, length,
+ data_->cumulative, data_->offset,
+ data_->expected, data_->received));
+ } while (!data_->responseOK());
+
+ // Finished with this socket, so close it. This will not generate an
+ // I/O error, but reset the origin to unknown in case we change this.
+ data_->origin = ASIO_UNKORIGIN;
data_->socket->close();
/// We are done
@@ -208,9 +316,8 @@ IOFetch::operator()(error_code ec, size_t length) {
// query finishes or when the timer times out. Either way, it sets the
// "stopped_" flag and cancels anything that is in progress.
//
-// As the function may be entered multiple times as things wind down, the
-// stopped_ flag checks if stop() has already been called. If it has,
-// subsequent calls are no-ops.
+// As the function may be entered multiple times as things wind down, it checks
+// if the stopped_ flag is already set. If it is, the call is a no-op.
void
IOFetch::stop(Result result) {
@@ -227,20 +334,46 @@ IOFetch::stop(Result result) {
// variable should be done inside a mutex (and the stopped_ variable
// declared as "volatile").
//
+ // The numeric arguments indicate the debug level, with the lower
+ // numbers indicating the most important information. The relative
+ // values are somewhat arbitrary.
+ //
+ // Although Logger::debug checks the debug flag internally, doing it
+ // below before calling Logger::debug avoids the overhead of a string
+ // conversion in the common case when debug is not enabled.
+ //
// TODO: Update testing of stopped_ if threads are used.
data_->stopped = true;
-
switch (result) {
case TIME_OUT:
- dlog("Query timed out");
+ if (logger.isDebugEnabled(1)) {
+ logger.debug(20, ASIO_RECVTMO,
+ data_->remote_snd->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote_snd->getPort()));
+ }
+ break;
+
+ case SUCCESS:
+ if (logger.isDebugEnabled(50)) {
+ logger.debug(30, ASIO_FETCHCOMP,
+ data_->remote_rcv->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote_rcv->getPort()));
+ }
break;
case STOPPED:
- dlog("Query stopped");
+ // Fetch has been stopped for some other reason. This is
+ // allowed but as it is unusual it is logged, but with a lower
+ // debug level than a timeout (which is totally normal).
+ logger.debug(1, ASIO_FETCHSTOP,
+ data_->remote_snd->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote_snd->getPort()));
break;
default:
- ;
+ logger.error(ASIO_UNKRESULT, static_cast<int>(result),
+ data_->remote_snd->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote_snd->getPort()));
}
// Stop requested, cancel and I/O's on the socket and shut it down,
@@ -254,10 +387,26 @@ IOFetch::stop(Result result) {
if (data_->callback) {
(*(data_->callback))(result);
}
+ }
+}
- // Mark that stop() has now been called.
+// Log an error - called on I/O failure
- }
+void IOFetch::logIOFailure(asio::error_code ec) {
+
+ // Should only get here with a known error code.
+ assert((data_->origin == ASIO_OPENSOCK) ||
+ (data_->origin == ASIO_SENDSOCK) ||
+ (data_->origin == ASIO_RECVSOCK) ||
+ (data_->origin == ASIO_UNKORIGIN));
+
+ static const char* PROTOCOL[2] = {"TCP", "UDP"};
+ logger.error(data_->origin,
+ ec.value(),
+ ((data_->remote_snd->getProtocol() == IPPROTO_TCP) ?
+ PROTOCOL[0] : PROTOCOL[1]),
+ data_->remote_snd->getAddress().toText().c_str(),
+ static_cast<int>(data_->remote_snd->getPort()));
}
} // namespace asiolink
diff --git a/src/lib/asiolink/io_fetch.h b/src/lib/asiolink/io_fetch.h
index 6fdce6c..3671adc 100644
--- a/src/lib/asiolink/io_fetch.h
+++ b/src/lib/asiolink/io_fetch.h
@@ -17,33 +17,24 @@
#include <config.h>
-#include <netinet/in.h>
-#include <sys/socket.h>
-#include <unistd.h> // for some IPC/network system calls
-
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
-#include <asio/deadline_timer.hpp>
#include <coroutine.h>
+#include <asio/error_code.hpp>
+
#include <dns/buffer.h>
#include <dns/question.h>
#include <dns/message.h>
-#include <asiolink/io_asio_socket.h>
-#include <asiolink/io_endpoint.h>
-#include <asiolink/io_service.h>
-#include <asiolink/tcp_socket.h>
-#include <asiolink/tcp_endpoint.h>
-#include <asiolink/udp_socket.h>
-#include <asiolink/udp_endpoint.h>
-#include <asiolink/qid_gen.h>
-
-
namespace asiolink {
+// Forward declarations
+class IOAddress;
+class IOFetchData;
+class IOService;
/// \brief Upstream Fetch Processing
///
@@ -53,6 +44,23 @@ namespace asiolink {
class IOFetch : public coroutine {
public:
+ /// \brief Protocol to use on the fetch
+ enum Protocol {
+ UDP = 0,
+ TCP = 1
+ };
+
+ /// \brief Origin of Asynchronous I/O Call
+ ///
+ /// Indicates what initiated an asynchronous I/O call and used in deciding
+ /// what error message to output if the I/O fails.
+ enum Origin {
+ NONE = 0, ///< No asynchronous call outstanding
+ OPEN = 1,
+ SEND = 2,
+ RECEIVE = 3,
+ CLOSE = 4
+ };
/// \brief Result of Upstream Fetch
///
@@ -61,9 +69,9 @@ public:
/// even if the contents of the packet indicate that some error occurred.
enum Result {
SUCCESS = 0, ///< Success, fetch completed
- TIME_OUT, ///< Failure, fetch timed out
- STOPPED, ///< Control code, fetch has been stopped
- NOTSET ///< For testing, indicates value not set
+ TIME_OUT = 1, ///< Failure, fetch timed out
+ STOPPED = 2, ///< Control code, fetch has been stopped
+ NOTSET = 3 ///< For testing, indicates value not set
};
// The next enum is a "trick" to allow constants to be defined in a class
@@ -71,7 +79,7 @@ public:
/// \brief Integer Constants
enum {
- MAX_LENGTH = 4096 ///< Maximum size of receive buffer
+ STAGING_LENGTH = 8192 ///< Size of staging buffer
};
/// \brief I/O Fetch Callback
@@ -97,82 +105,12 @@ public:
virtual ~Callback()
{}
- /// \brief Callback method called when the fetch completes
+ /// \brief Callback method
///
- /// \brief result Result of the fetch
- virtual void operator()(Result result) = 0;
- };
-
- /// \brief IOFetch Data
- ///
- /// The data for IOFetch is held in a separate struct pointed to by a
- /// shared_ptr object. This is because the IOFetch object will be copied
- /// often (it is used as a coroutine and passed as callback to many
- /// async_*() functions) and we want keep the same data). Organising the
- /// data in this way keeps copying to a minimum.
- struct IOFetchData {
-
- // The next two members are shared pointers to a base class because what
- // is actually instantiated depends on whether the fetch is over UDP or
- // TCP, which is not known until construction of the IOFetch. Use of
- // a shared pointer here is merely to ensure deletion when the data
- // object is deleted.
- boost::shared_ptr<IOAsioSocket<IOFetch> > socket;
- ///< Socket to use for I/O
- boost::shared_ptr<IOEndpoint> remote; ///< Where the fetch was sent
- isc::dns::MessagePtr query_message;///< Query message to be sent
- isc::dns::OutputBufferPtr msgbuf; ///< Wire buffer for question
- isc::dns::OutputBufferPtr buffer; ///< Received data held here
- boost::shared_array<char> data; ///< Temporary array for data
- IOFetch::Callback* callback; ///< Called on I/O Completion
- size_t cumulative; ///< Cumulative received amount
- bool stopped; ///< Have we stopped running?
- asio::deadline_timer timer; ///< Timer to measure timeouts
- int timeout; ///< Timeout in ms
-
- /// \brief Constructor
+ /// This is the method called when the fetch completes.
///
- /// Just fills in the data members of the IOFetchData structure
- ///
- /// \param protocol either IPPROTO_UDP or IPPROTO_TCP
- /// \param service I/O Service object to handle the asynchronous
- /// operations.
- /// \param query DNS question to send to the upstream server.
- /// \param address IP address of upstream server
- /// \param port Port to use for the query
- /// \param buff Output buffer into which the response (in wire format)
- /// is written (if a response is received).
- /// \param cb Callback object containing the callback to be called
- /// when we terminate. The caller is responsible for managing this
- /// object and deleting it if necessary.
- /// \param wait Timeout for the fetch (in ms).
- ///
- /// TODO: May need to alter constructor (see comment 4 in Trac ticket #554)
- IOFetchData(int protocol, IOService& service,
- isc::dns::MessagePtr query_msg, const IOAddress& address,
- uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
- int wait)
- :
- socket((protocol == IPPROTO_UDP) ?
- static_cast<IOAsioSocket<IOFetch>*>(
- new UDPSocket<IOFetch>(service)) :
- static_cast<IOAsioSocket<IOFetch>*>(
- new TCPSocket<IOFetch>(service))
- ),
- remote((protocol == IPPROTO_UDP) ?
- static_cast<IOEndpoint*>(new UDPEndpoint(address, port)) :
- static_cast<IOEndpoint*>(new TCPEndpoint(address, port))
- ),
- query_message(query_msg),
- msgbuf(new isc::dns::OutputBuffer(512)),
- buffer(buff),
- data(new char[IOFetch::MAX_LENGTH]),
- callback(cb),
- cumulative(0),
- stopped(false),
- timer(service.get_io_service()),
- timeout(wait)
- {}
+ /// \param result Result of the fetch
+ virtual void operator()(Result result) = 0;
};
/// \brief Constructor.
@@ -181,7 +119,7 @@ public:
///
/// TODO: Need to randomise the source port
///
- /// \param protocol Fetch protocol, either IPPROTO_UDP or IPPROTO_TCP
+ /// \param protocol Fetch protocol, either IOFetch::TCP or IOFetch::UDP
/// \param service I/O Service object to handle the asynchronous
/// operations.
/// \param question DNS question to send to the upstream server.
@@ -195,7 +133,7 @@ public:
/// (default = 53)
/// \param wait Timeout for the fetch (in ms). The default value of
/// -1 indicates no timeout.
- IOFetch(int protocol, IOService& service,
+ IOFetch(Protocol protocol, IOService& service,
const isc::dns::Question& question, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
@@ -207,11 +145,16 @@ public:
/// other parameters are same.
///
/// \param query_message the shared_ptr to a full query message.
- IOFetch(int protocol, IOService& service,
+ IOFetch(Protocol protocol, IOService& service,
isc::dns::ConstMessagePtr query_message, const IOAddress& address,
uint16_t port, isc::dns::OutputBufferPtr& buff, Callback* cb,
int wait = -1);
+ /// \brief Return Current Protocol
+ ///
+ /// \return Protocol associated with this IOFetch object.
+ Protocol getProtocol() const;
+
/// \brief Coroutine entry point
///
/// The operator() method is the method in which the coroutine code enters
@@ -219,8 +162,7 @@ public:
///
/// \param ec Error code, the result of the last asynchronous I/O operation.
/// \param length Amount of data received on the last asynchronous read
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0);
+ void operator()(asio::error_code ec = asio::error_code(), size_t length = 0);
/// \brief Terminate query
///
@@ -234,12 +176,24 @@ private:
/// \brief IOFetch Initialization Function.
/// All the parameters are same with the constructor, except
/// parameter "query_message"
+ ///
+ /// \brief Initialize the message to be sent out.
/// \param query_message the message to be sent out.
- void initIOFetch(isc::dns::MessagePtr& query_message, int protocol,
+ void initIOFetch(isc::dns::MessagePtr& query_message, Protocol protocol,
IOService& service, const isc::dns::Question& question,
const IOAddress& address, uint16_t port,
isc::dns::OutputBufferPtr& buff, Callback* cb, int wait);
+ /// \brief Log I/O Failure
+ ///
+ /// Records an I/O failure to the log file
+ ///
+ /// \param ec ASIO error code
+ void logIOFailure(asio::error_code ec);
+
+ // Member variables. All data is in a structure pointed to by a shared
+ // pointer. The IOFetch object is copied a number of times during its
+ // life, and only requiring a pointer to be copied reduces overhead.
boost::shared_ptr<IOFetchData> data_; ///< Private data
};
diff --git a/src/lib/asiolink/io_message.h b/src/lib/asiolink/io_message.h
index 532f449..e857bd9 100644
--- a/src/lib/asiolink/io_message.h
+++ b/src/lib/asiolink/io_message.h
@@ -98,7 +98,3 @@ private:
} // asiolink
#endif // __IO_MESSAGE_H
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/asiolink/io_socket.cc b/src/lib/asiolink/io_socket.cc
index fb325e9..c386ca1 100644
--- a/src/lib/asiolink/io_socket.cc
+++ b/src/lib/asiolink/io_socket.cc
@@ -1,5 +1,3 @@
-// Copyright (C) 2010 CZ NIC
-// Copyed from other version of auth/asiolink.cc which is:
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
diff --git a/src/lib/asiolink/recursive_query.h b/src/lib/asiolink/recursive_query.h
deleted file mode 100644
index e3ac56c..0000000
--- a/src/lib/asiolink/recursive_query.h
+++ /dev/null
@@ -1,128 +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 __ASIOLINK_RECURSIVE_QUERY_H
-#define __ASIOLINK_RECURSIVE_QUERY_H 1
-
-#include <asiolink/dns_service.h>
-#include <asiolink/dns_server.h>
-#include <dns/buffer.h>
-#include <cache/resolver_cache.h>
-
-namespace asiolink {
-/// \brief The \c RecursiveQuery class provides a layer of abstraction around
-/// the ASIO code that carries out an upstream query.
-///
-/// This design is very preliminary; currently it is only capable of
-/// handling simple forward requests to a single resolver.
-class RecursiveQuery {
- ///
- /// \name Constructors
- ///
- //@{
-public:
- /// \brief Constructor
- ///
- /// This is currently the only way to construct \c RecursiveQuery
- /// object. If the addresses of the forward nameservers is specified,
- /// and every upstream query will be sent to one random address, and
- /// the result sent back directly. If not, it will do full resolving.
- ///
- /// \param dns_service The DNS Service to perform the recursive
- /// query on.
- /// \param upstream Addresses and ports of the upstream servers
- /// to forward queries to.
- /// \param upstream_root Addresses and ports of the root servers
- /// to use when resolving.
- /// \param query_timeout Timeout value for queries we sent, in ms
- /// \param client_timeout Timeout value for when we send back an
- /// error, in ms
- /// \param lookup_timeout Timeout value for when we give up, in ms
- /// \param retries how many times we try again (0 means just send and
- /// and return if it returs).
- RecursiveQuery(DNSService& dns_service,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream,
- const std::vector<std::pair<std::string, uint16_t> >&
- upstream_root,
- int query_timeout = 2000,
- int client_timeout = 4000,
- int lookup_timeout = 30000,
- unsigned retries = 3);
- //@}
-
- /// \brief Initiate resolving
- ///
- /// When sendQuery() is called, a (set of) message(s) is sent
- /// asynchronously. If upstream servers are set, one is chosen
- /// and the response (if any) from that server will be returned.
- ///
- /// If not upstream is set, a root server is chosen from the
- /// root_servers, and the RunningQuery shall do a full resolve
- /// (i.e. if the answer is a delegation, it will be followed, etc.)
- /// until there is an answer or an error.
- ///
- /// When there is a response or an error and we give up, the given
- /// CallbackPtr object shall be called (with either success() or
- /// failure(). See ResolverInterface::Callback for more information.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param callback Callback object. See
- /// \c ResolverInterface::Callback for more information
- void resolve(const isc::dns::QuestionPtr& question,
- const isc::resolve::ResolverInterface::CallbackPtr callback);
-
-
- /// \brief Initiates resolving for the given question.
- ///
- /// This actually calls the previous sendQuery() with a default
- /// callback object, which calls resume() on the given DNSServer
- /// object.
- ///
- /// \param question The question being answered <qname/qclass/qtype>
- /// \param answer_message An output Message into which the final response will be copied
- /// \param buffer An output buffer into which the intermediate responses will be copied
- /// \param server A pointer to the \c DNSServer object handling the client
- void resolve(const isc::dns::Question& question,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server);
-
- /// \brief Initiates forwarding for the given query.
- ///
- /// Others parameters are same with the parameters of
- /// function resolve().
- ///
- /// \param query_message the full query got from client.
- void forward(isc::dns::ConstMessagePtr query_message,
- isc::dns::MessagePtr answer_message,
- isc::dns::OutputBufferPtr buffer,
- DNSServer* server);
-private:
- DNSService& dns_service_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_;
- boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
- upstream_root_;
- int query_timeout_;
- int client_timeout_;
- int lookup_timeout_;
- unsigned retries_;
- // Cache. TODO: I think we want this initialized in Resolver class,
- // not here
- isc::cache::ResolverCache cache_;
-};
-
-} // namespace asiolink
-#endif // __ASIOLINK_RECURSIVE_QUERY_H
diff --git a/src/lib/asiolink/tcp_endpoint.h b/src/lib/asiolink/tcp_endpoint.h
index 8f6270f..158ca4a 100644
--- a/src/lib/asiolink/tcp_endpoint.h
+++ b/src/lib/asiolink/tcp_endpoint.h
@@ -24,32 +24,33 @@
namespace asiolink {
/// \brief The \c TCPEndpoint class is a concrete derived class of
-/// \c IOEndpoint that represents an endpoint of a TCP connection.
+/// \c IOEndpoint that represents an endpoint of a TCP packet.
///
-/// In the current implementation, an object of this class is always
-/// instantiated within the wrapper routines. Applications are expected to
-/// get access to the object via the abstract base class, \c IOEndpoint.
-/// This design may be changed when we generalize the wrapper interface.
-///
-/// Note: this implementation is optimized for the case where this object
-/// is created from an ASIO endpoint object in a receiving code path
-/// by avoiding to make a copy of the base endpoint. For TCP it may not be
-/// a big deal, but when we receive UDP packets at a high rate, the copy
-/// overhead might be significant.
+/// Other notes about \c TCPEndpoint applies to this class, too.
class TCPEndpoint : public IOEndpoint {
public:
///
- /// \name Constructors and Destructor
+ /// \name Constructors and Destructor.
///
//@{
+
+ /// \brief Default Constructor
+ ///
+ /// Creates an internal endpoint. This is expected to be set by some
+ /// external call.
+ TCPEndpoint() :
+ asio_endpoint_placeholder_(new asio::ip::tcp::endpoint()),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
/// \brief Constructor from a pair of address and port.
///
/// \param address The IP address of the endpoint.
/// \param port The TCP port number of the endpoint.
TCPEndpoint(const IOAddress& address, const unsigned short port) :
asio_endpoint_placeholder_(
- new asio::ip::tcp::endpoint(
- asio::ip::address::from_string(address.toText()), port)),
+ new asio::ip::tcp::endpoint(asio::ip::address::from_string(address.toText()),
+ port)),
asio_endpoint_(*asio_endpoint_placeholder_)
{}
@@ -59,39 +60,53 @@ public:
/// corresponding ASIO class, \c tcp::endpoint.
///
/// \param asio_endpoint The ASIO representation of the TCP endpoint.
- TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
+ TCPEndpoint(asio::ip::tcp::endpoint& asio_endpoint) :
asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
{}
+ /// \brief Constructor from an ASIO TCP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c tcp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ TCPEndpoint(const asio::ip::tcp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new asio::ip::tcp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
/// \brief The destructor.
- ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
+ virtual ~TCPEndpoint() { delete asio_endpoint_placeholder_; }
//@}
- IOAddress getAddress() const {
+ virtual IOAddress getAddress() const {
return (asio_endpoint_.address());
}
- uint16_t getPort() const {
+ virtual uint16_t getPort() const {
return (asio_endpoint_.port());
}
- short getProtocol() const {
+ virtual short getProtocol() const {
return (asio_endpoint_.protocol().protocol());
}
- short getFamily() const {
+ virtual short getFamily() const {
return (asio_endpoint_.protocol().family());
}
// This is not part of the exosed IOEndpoint API but allows
// direct access to the ASIO implementation of the endpoint
- const asio::ip::tcp::endpoint& getASIOEndpoint() const {
+ inline const asio::ip::tcp::endpoint& getASIOEndpoint() const {
+ return (asio_endpoint_);
+ }
+ inline asio::ip::tcp::endpoint& getASIOEndpoint() {
return (asio_endpoint_);
}
private:
- const asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
- const asio::ip::tcp::endpoint& asio_endpoint_;
+ asio::ip::tcp::endpoint* asio_endpoint_placeholder_;
+ asio::ip::tcp::endpoint& asio_endpoint_;
};
} // namespace asiolink
diff --git a/src/lib/asiolink/tcp_server.cc b/src/lib/asiolink/tcp_server.cc
index 3e0cdb4..db59551 100644
--- a/src/lib/asiolink/tcp_server.cc
+++ b/src/lib/asiolink/tcp_server.cc
@@ -47,7 +47,7 @@ TCPServer::TCPServer(io_service& io_service,
const SimpleCallback* checkin,
const DNSLookup* lookup,
const DNSAnswer* answer) :
- io_(io_service), done_(false), stopped_by_hand_(false),
+ io_(io_service), done_(false),
checkin_callback_(checkin), lookup_callback_(lookup),
answer_callback_(answer)
{
@@ -70,12 +70,6 @@ TCPServer::operator()(error_code ec, size_t length) {
/// a switch statement, inline variable declarations are not
/// permitted. Certain variables used below can be declared here.
- /// If user has stopped the server, we won't enter the
- /// coroutine body, just return
- if (stopped_by_hand_) {
- return;
- }
-
boost::array<const_buffer,2> bufs;
OutputBuffer lenbuf(TCP_MESSAGE_LENGTHSIZE);
@@ -88,6 +82,7 @@ TCPServer::operator()(error_code ec, size_t length) {
/// try again
do {
CORO_YIELD acceptor_->async_accept(*socket_, *this);
+
// Abort on fatal errors
// TODO: Log error?
if (ec) {
@@ -115,6 +110,7 @@ TCPServer::operator()(error_code ec, size_t length) {
CORO_YIELD async_read(*socket_, asio::buffer(data_.get(),
TCP_MESSAGE_LENGTHSIZE), *this);
if (ec) {
+ socket_->close();
CORO_YIELD return;
}
@@ -127,9 +123,11 @@ TCPServer::operator()(error_code ec, size_t length) {
}
if (ec) {
+ socket_->close();
CORO_YIELD return;
}
+
// Create an \c IOMessage object to store the query.
//
// (XXX: It would be good to write a factory function
@@ -160,6 +158,7 @@ TCPServer::operator()(error_code ec, size_t length) {
// If we don't have a DNS Lookup provider, there's no point in
// continuing; we exit the coroutine permanently.
if (lookup_callback_ == NULL) {
+ socket_->close();
CORO_YIELD return;
}
@@ -177,9 +176,15 @@ TCPServer::operator()(error_code ec, size_t length) {
// The 'done_' flag indicates whether we have an answer
// to send back. If not, exit the coroutine permanently.
if (!done_) {
+ // TODO: should we keep the connection open for a short time
+ // to see if new requests come in?
+ socket_->close();
CORO_YIELD return;
}
+ if (ec) {
+ CORO_YIELD return;
+ }
// Call the DNS answer provider to render the answer into
// wire format
(*answer_callback_)(*io_message_, query_message_,
@@ -195,6 +200,10 @@ TCPServer::operator()(error_code ec, size_t length) {
// (though we have nothing further to do, so the coroutine
// will simply exit at that time).
CORO_YIELD async_write(*socket_, bufs, *this);
+
+ // TODO: should we keep the connection open for a short time
+ // to see if new requests come in?
+ socket_->close();
}
}
@@ -207,14 +216,15 @@ TCPServer::asyncLookup() {
}
void TCPServer::stop() {
- // server should not be stopped twice
- if (stopped_by_hand_) {
- return;
- }
+ /// we use close instead of cancel, with the same reason
+ /// with udp server stop, refer to the udp server code
- stopped_by_hand_ = true;
acceptor_->close();
- socket_->close();
+ // User may stop the server even when it hasn't started to
+ // run, in that that socket_ is empty
+ if (socket_) {
+ socket_->close();
+ }
}
/// Post this coroutine on the ASIO service queue so that it will
/// resume processing where it left off. The 'done' parameter indicates
diff --git a/src/lib/asiolink/tcp_server.h b/src/lib/asiolink/tcp_server.h
index 9df335d..2fe0d37 100644
--- a/src/lib/asiolink/tcp_server.h
+++ b/src/lib/asiolink/tcp_server.h
@@ -107,9 +107,6 @@ private:
size_t bytes_;
bool done_;
- // whether user has stopped the server
- bool stopped_by_hand_;
-
// Callback functions provided by the caller
const SimpleCallback* checkin_callback_;
const DNSLookup* lookup_callback_;
diff --git a/src/lib/asiolink/tcp_socket.h b/src/lib/asiolink/tcp_socket.h
index 5a85aaa..e6e0863 100644
--- a/src/lib/asiolink/tcp_socket.h
+++ b/src/lib/asiolink/tcp_socket.h
@@ -24,11 +24,18 @@
#include <sys/socket.h>
#include <unistd.h> // for some IPC/network system calls
-#include <iostream>
+#include <algorithm>
+#include <cassert>
#include <cstddef>
+#include <boost/bind.hpp>
+#include <boost/numeric/conversion/cast.hpp>
+
#include <config.h>
+#include <dns/buffer.h>
+
+#include <asiolink/asiolink_utilities.h>
#include <asiolink/io_asio_socket.h>
#include <asiolink/io_endpoint.h>
#include <asiolink/io_service.h>
@@ -36,6 +43,15 @@
namespace asiolink {
+/// \brief Buffer Too Large
+///
+/// Thrown on an attempt to send a buffer > 64k
+class BufferTooLarge : public IOError {
+public:
+ BufferTooLarge(const char* file, size_t line, const char* what) :
+ IOError(file, line, what) {}
+};
+
/// \brief The \c TCPSocket class is a concrete derived class of \c IOAsioSocket
/// that represents a TCP socket.
///
@@ -48,18 +64,18 @@ private:
TCPSocket& operator=(const TCPSocket&);
public:
-
+
/// \brief Constructor from an ASIO TCP socket.
///
- /// \param socket The ASIO representation of the TCP socket. It
- /// is assumed that the caller will open and close the socket, so
- /// these operations are a no-op for that socket.
+ /// \param socket The ASIO representation of the TCP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
TCPSocket(asio::ip::tcp::socket& socket);
/// \brief Constructor
///
/// Used when the TCPSocket is being asked to manage its own internal
- /// socket. It is assumed that open() and close() will not be used.
+ /// socket. In this case, the open() and close() methods are used.
///
/// \param service I/O Service object used to manage the socket.
TCPSocket(IOService& service);
@@ -67,68 +83,79 @@ public:
/// \brief Destructor
virtual ~TCPSocket();
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_TCP); }
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
- /// \brief Open Socket
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_TCP);
+ }
+
+ /// \brief Is "open()" synchronous?
///
- /// Opens the TCP socket. In the model for transport-layer agnostic I/O,
- /// an "open" operation includes a connection to the remote end (which
- /// may take time). This does not happen for TCP, so the method returns
- /// "false" to indicate that the operation completed synchronously.
+ /// Indicates that the opening of a TCP socket is asynchronous.
+ virtual bool isOpenSynchronous() const {
+ return (false);
+ }
+
+ /// \brief Open Socket
///
- /// \param endpoint Endpoint to which the socket will connect to.
- /// \param callback Unused.
+ /// Opens the TCP socket. This is an asynchronous operation, completion of
+ /// which will be signalled via a call to the callback function.
///
- /// \return false to indicate that the "operation" completed synchronously.
- virtual bool open(const IOEndpoint* endpoint, C&);
+ /// \param endpoint Endpoint to which the socket will connect.
+ /// \param callback Callback object.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
/// \brief Send Asynchronously
///
- /// This corresponds to async_send_to() for TCP sockets and async_send()
- /// for TCP. In both cases an endpoint argument is supplied indicating the
- /// target of the send - this is ignored for TCP.
+ /// Calls the underlying socket's async_send() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
///
/// \param data Data to send
/// \param length Length of data to send
- /// \param endpoint Target of the send
+ /// \param endpoint Target of the send. (Unused for a TCP socket because
+ /// that was determined when the connection was opened.)
/// \param callback Callback object.
virtual void asyncSend(const void* data, size_t length,
- const IOEndpoint* endpoint, C& callback);
+ const IOEndpoint* endpoint, C& callback);
/// \brief Receive Asynchronously
///
- /// This correstponds to async_receive_from() for TCP sockets and
- /// async_receive() for TCP. In both cases, an endpoint argument is
- /// supplied to receive the source of the communication. For TCP it will
- /// be filled in with details of the connection.
+ /// Calls the underlying socket's async_receive() method to read a packet
+ /// of data from a remote endpoint. Arrival of the data is signalled via a
+ /// call to the callback function.
///
/// \param data Buffer to receive incoming message
/// \param length Length of the data buffer
- /// \param cumulative Amount of data that should already be in the buffer.
- /// (This is ignored - every UPD receive fills the buffer from the start.)
+ /// \param offset Offset into buffer where data is to be put
/// \param endpoint Source of the communication
/// \param callback Callback object
- virtual void asyncReceive(void* data, size_t length, size_t cumulative,
- IOEndpoint* endpoint, C& callback);
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
- /// \brief Checks if the data received is complete.
+ /// \brief Process received data packet
///
- /// As all the data is received in one I/O, so this is, this is effectively
- /// a no-op (although it does update the amount of data received).
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
///
- /// \param data Data buffer containing data to date. (This is ignored
- /// for TCP receives.)
- /// \param length Amount of data received in last asynchronous I/O
- /// \param cumulative On input, amount of data received before the last
- /// I/O. On output, the total amount of data received to date.
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
///
- /// \return true if the receive is complete, false if another receive is
- /// needed.
- virtual bool receiveComplete(void*, size_t length, size_t& cumulative) {
- cumulative = length;
- return (true);
- }
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff);
/// \brief Cancel I/O On Socket
virtual void cancel();
@@ -144,13 +171,28 @@ private:
asio::ip::tcp::socket* socket_ptr_; ///< Pointer to own socket
asio::ip::tcp::socket& socket_; ///< Socket
bool isopen_; ///< true when socket is open
+
+ // TODO: Remove temporary buffer
+ // The current implementation copies the buffer passed to asyncSend() into
+ // a temporary buffer and precedes it with a two-byte count field. As
+ // ASIO should really be just about sending and receiving data, the TCP
+ // code should not do this. If the protocol using this requires a two-byte
+ // count, it should add it before calling this code. (This may be best
+ // achieved by altering isc::dns::buffer to have pairs of methods:
+ // getLength()/getTCPLength(), getData()/getTCPData(), with the getTCPXxx()
+ // methods taking into account a two-byte count field.)
+ //
+ // The option of sending the data in two operations, the count followed by
+ // the data was discounted as that would lead to two callbacks which would
+ // cause problems with the stackless coroutine code.
+ isc::dns::OutputBufferPtr send_buffer_; ///< Send buffer
};
// Constructor - caller manages socket
template <typename C>
TCPSocket<C>::TCPSocket(asio::ip::tcp::socket& socket) :
- socket_ptr_(NULL), socket_(socket), isopen_(true)
+ socket_ptr_(NULL), socket_(socket), isopen_(true), send_buffer_()
{
}
@@ -171,16 +213,14 @@ TCPSocket<C>::~TCPSocket()
delete socket_ptr_;
}
-// Open the socket. Throws an error on failure
-// TODO: Make the open more resilient
+// Open the socket.
-template <typename C> bool
-TCPSocket<C>::open(const IOEndpoint* endpoint, C&) {
+template <typename C> void
+TCPSocket<C>::open(const IOEndpoint* endpoint, C& callback) {
// Ignore opens on already-open socket. Don't throw a failure because
// of uncertainties as to what precedes whan when using asynchronous I/O.
// At also allows us a treat a passed-in socket as a self-managed socket.
-
if (!isopen_) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::tcp::v4());
@@ -190,10 +230,25 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C&) {
}
isopen_ = true;
- // TODO: Complete TCPSocket::open()
+ // Set options on the socket:
+ // Reuse address - allow the socket to bind to a port even if the port
+ // is in the TIMED_WAIT state.
+ socket_.set_option(asio::socket_base::reuse_address(true));
}
- return (false);
+
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it does not
+ // contain a method for getting at the underlying endpoint type - that is in
+ /// the derived class and the two classes differ on return type.
+ assert(endpoint->getProtocol() == IPPROTO_TCP);
+ const TCPEndpoint* tcp_endpoint =
+ static_cast<const TCPEndpoint*>(endpoint);
+
+ // Connect to the remote endpoint. On success, the handler will be
+ // called (with one argument - the length argument will default to
+ // zero).
+ socket_.async_connect(tcp_endpoint->getASIOEndpoint(), callback);
}
// Send a message. Should never do this if the socket is not open, so throw
@@ -201,24 +256,29 @@ TCPSocket<C>::open(const IOEndpoint* endpoint, C&) {
template <typename C> void
TCPSocket<C>::asyncSend(const void* data, size_t length,
- const IOEndpoint* endpoint, C& callback)
+ const IOEndpoint*, C& callback)
{
if (isopen_) {
- // Upconvert to a TCPEndpoint. We need to do this because although
- // IOEndpoint is the base class of TCPEndpoint and TCPEndpoint, it
- // doing cont contain a method for getting at the underlying endpoint
- // type - those are in the derived class and the two classes differ on
- // return type.
-
- assert(endpoint->getProtocol() == IPPROTO_TCP);
- const TCPEndpoint* tcp_endpoint =
- static_cast<const TCPEndpoint*>(endpoint);
- std::cerr << "TCPSocket::asyncSend(): sending to " <<
- tcp_endpoint->getAddress().toText() <<
- ", port " << tcp_endpoint->getPort() << "\n";
-
- // TODO: Complete TCPSocket::asyncSend()
+ // Need to copy the data into a temporary buffer and precede it with
+ // a two-byte count field.
+ // TODO: arrange for the buffer passed to be preceded by the count
+ try {
+ // Ensure it fits into 16 bits
+ uint16_t count = boost::numeric_cast<uint16_t>(length);
+
+ // Copy data into a buffer preceded by the count field.
+ send_buffer_.reset(new isc::dns::OutputBuffer(length + 2));
+ send_buffer_->writeUint16(count);
+ send_buffer_->writeData(data, length);
+
+ // ... and send it
+ socket_.async_send(asio::buffer(send_buffer_->getData(),
+ send_buffer_->getLength()), callback);
+ } catch (boost::numeric::bad_numeric_cast& e) {
+ isc_throw(BufferTooLarge,
+ "attempt to send buffer larger than 64kB");
+ }
} else {
isc_throw(SocketNotOpen,
@@ -226,26 +286,40 @@ TCPSocket<C>::asyncSend(const void* data, size_t length,
}
}
-// Receive a message. Note that the "cumulative" argument is ignored - every TCP
-// receive is put into the buffer beginning at the start - there is no concept
-// receiving a subsequent part of a message. Same critera as before concerning
-// the need for the socket to be open.
-
+// Receive a message. Note that the "offset" argument is used as an index
+// into the buffer in order to decide where to put the data. It is up to the
+// caller to initialize the data to zero
template <typename C> void
-TCPSocket<C>::asyncReceive(void* data, size_t length, size_t,
+TCPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
-
- // Upconvert the endpoint again.
+ // Upconvert to a TCPEndpoint. We need to do this because although
+ // IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
+ // return type.
assert(endpoint->getProtocol() == IPPROTO_TCP);
- const TCPEndpoint* tcp_endpoint =
- static_cast<const TCPEndpoint*>(endpoint);
- std::cerr << "TCPSocket::asyncReceive(): receiving from " <<
- tcp_endpoint->getAddress().toText() <<
- ", port " << tcp_endpoint->getPort() << "\n";
+ TCPEndpoint* tcp_endpoint = static_cast<TCPEndpoint*>(endpoint);
+
+ // Write the endpoint details from the communications link. Ideally
+ // we should make IOEndpoint assignable, but this runs in to all sorts
+ // of problems concerning the management of the underlying Boost
+ // endpoint (e.g. if it is not self-managed, is the copied one
+ // self-managed?) The most pragmatic solution is to let Boost take care
+ // of everything and copy details of the underlying endpoint.
+ tcp_endpoint->getASIOEndpoint() = socket_.remote_endpoint();
+
+ // Ensure we can write into the buffer and if so, set the pointer to
+ // where the data will be written.
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "TCP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
- // TODO: Complete TCPSocket::asyncReceive()
+ // ... and kick off the read.
+ socket_.async_receive(asio::buffer(buffer_start, length - offset), callback);
} else {
isc_throw(SocketNotOpen,
@@ -253,7 +327,72 @@ TCPSocket<C>::asyncReceive(void* data, size_t length, size_t,
}
}
+// Is the receive complete?
+
+template <typename C> bool
+TCPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+{
+ // Point to the data in the staging buffer and note how much there is.
+ const uint8_t* data = static_cast<const uint8_t*>(staging);
+ size_t data_length = length;
+
+ // Is the number is "expected" valid? It won't be unless we have received
+ // at least two bytes of data in total for this set of receives.
+ if (cumulative < 2) {
+
+ // "expected" is not valid. Did this read give us enough data to
+ // work it out?
+ cumulative += length;
+ if (cumulative < 2) {
+
+ // Nope, still not valid. This must have been the first packet and
+ // was only one byte long. Tell the fetch code to read the next
+ // packet into the staging buffer beyond the data that is already
+ // there so that the next time we are called we have a complete
+ // TCP count.
+ offset = cumulative;
+ return (false);
+ }
+
+ // Have enough data to interpret the packet count, so do so now.
+ expected = readUint16(data);
+
+ // We have two bytes less of data to process. Point to the start of the
+ // data and adjust the packet size. Note that at this point,
+ // "cumulative" is the true amount of data in the staging buffer, not
+ // "length".
+ data += 2;
+ data_length = cumulative - 2;
+ } else {
+
+ // Update total amount of data received.
+ cumulative += length;
+ }
+
+ // Regardless of anything else, the next read goes into the start of the
+ // staging buffer.
+ offset = 0;
+
+ // Work out how much data we still have to put in the output buffer. (This
+ // could be zero if we have just interpreted the TCP count and that was
+ // set to zero.)
+ if (expected >= outbuff->getLength()) {
+
+ // Still need data in the output packet. Copy what we can from the
+ // staging buffer to the output buffer.
+ size_t copy_amount = std::min(expected - outbuff->getLength(), data_length);
+ outbuff->writeData(data, copy_amount);
+ }
+
+ // We can now say if we have all the data.
+ return (expected == outbuff->getLength());
+}
+
// Cancel I/O on the socket. No-op if the socket is not open.
+
template <typename C> void
TCPSocket<C>::cancel() {
if (isopen_) {
diff --git a/src/lib/asiolink/tests/Makefile.am b/src/lib/asiolink/tests/Makefile.am
index ba5d962..f67e547 100644
--- a/src/lib/asiolink/tests/Makefile.am
+++ b/src/lib/asiolink/tests/Makefile.am
@@ -18,29 +18,30 @@ TESTS += run_unittests
run_unittests_SOURCES = run_unittests.cc
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
+run_unittests_SOURCES += asiolink_utilities_unittest.cc
run_unittests_SOURCES += io_address_unittest.cc
run_unittests_SOURCES += io_endpoint_unittest.cc
run_unittests_SOURCES += io_fetch_unittest.cc
run_unittests_SOURCES += io_socket_unittest.cc
run_unittests_SOURCES += io_service_unittest.cc
run_unittests_SOURCES += interval_timer_unittest.cc
-run_unittests_SOURCES += recursive_query_unittest.cc
+run_unittests_SOURCES += tcp_endpoint_unittest.cc
+run_unittests_SOURCES += tcp_socket_unittest.cc
run_unittests_SOURCES += udp_endpoint_unittest.cc
run_unittests_SOURCES += udp_socket_unittest.cc
+run_unittests_SOURCES += dns_server_unittest.cc
run_unittests_SOURCES += qid_gen_unittest.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
-run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
-run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
-run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
-run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
-run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
diff --git a/src/lib/asiolink/tests/asiolink_utilities_unittest.cc b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc
new file mode 100644
index 0000000..51f565f
--- /dev/null
+++ b/src/lib/asiolink/tests/asiolink_utilities_unittest.cc
@@ -0,0 +1,74 @@
+// 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.
+
+/// \brief Test of asiolink utilties
+///
+/// Tests the fuctionality of the asiolink utilities code by comparing them
+/// with the equivalent methods in isc::dns::[Input/Output]Buffer.
+
+#include <cstddef>
+
+#include <gtest/gtest.h>
+
+#include <dns/buffer.h>
+#include <asiolink/asiolink_utilities.h>
+
+using namespace asiolink;
+using namespace isc::dns;
+
+TEST(asioutil, readUint16) {
+
+ // Reference buffer
+ uint8_t data[2];
+ isc::dns::InputBuffer buffer(data, sizeof(data));
+
+ // Avoid possible compiler warnings by only setting uint8_t variables to
+ // uint8_t values.
+ uint8_t i8 = 0;
+ uint8_t j8 = 0;
+ for (int i = 0; i < (2 << 8); ++i, ++i8) {
+ for (int j = 0; j < (2 << 8); ++j, ++j8) {
+ data[0] = i8;
+ data[1] = j8;
+ buffer.setPosition(0);
+ EXPECT_EQ(buffer.readUint16(), readUint16(data));
+ }
+ }
+}
+
+
+TEST(asioutil, writeUint16) {
+
+ // Reference buffer
+ isc::dns::OutputBuffer buffer(2);
+ uint8_t test[2];
+
+ // Avoid possible compiler warnings by only setting uint16_t variables to
+ // uint16_t values.
+ uint16_t i16 = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++i16) {
+
+ // Write the reference data
+ buffer.clear();
+ buffer.writeUint16(i16);
+
+ // ... and the test data
+ writeUint16(i16, test);
+
+ // ... and compare
+ const uint8_t* ref = static_cast<const uint8_t*>(buffer.getData());
+ EXPECT_EQ(ref[0], test[0]);
+ EXPECT_EQ(ref[1], test[1]);
+ }
+}
diff --git a/src/lib/asiolink/tests/dns_server_unittest.cc b/src/lib/asiolink/tests/dns_server_unittest.cc
new file mode 100644
index 0000000..5b8b683
--- /dev/null
+++ b/src/lib/asiolink/tests/dns_server_unittest.cc
@@ -0,0 +1,501 @@
+// 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 <config.h>
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_error.h>
+#include <asiolink/udp_server.h>
+#include <asiolink/tcp_server.h>
+#include <asiolink/dns_answer.h>
+#include <asiolink/dns_lookup.h>
+#include <string>
+#include <csignal>
+#include <unistd.h> //for alarm
+
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+
+/// The following tests focus on stop interface for udp and
+/// tcp server, there are lots of things can be shared to test
+/// both tcp and udp server, so they are in the same unittest
+
+/// The general work flow for dns server, is that wait for user
+/// query, once get one query, we will check the data is valid or
+/// not, if it passed, we will try to loop up the question, then
+/// compose the answer and finally send it back to user. The server
+/// may be stopped at any point during this porcess, so the test strategy
+/// is that we define 5 stop point and stop the server at these
+/// 5 points, to check whether stop is successful
+/// The 5 test points are :
+/// Before the server start to run
+/// After we get the query and check whether it's valid
+/// After we lookup the query
+/// After we compoisite the answer
+/// After user get the final result.
+
+/// The standard about whether we stop the server successfully or not
+/// is based on the fact that if the server is still running, the io
+/// service won't quit since it will wait for some asynchronized event for
+/// server. So if the io service block function run returns we assume
+/// that the server is stopped. To avoid stop interface failure which
+/// will block followed tests, using alarm signal to stop the blocking
+/// io service
+///
+/// The whole test context including one server and one client, and
+/// five stop checkpoints, we call them ServerStopper exclude the first
+/// stop point. Once the unittest fired, the client will send message
+/// to server, and the stopper may stop the server at the checkpoint, then
+/// we check the client get feedback or not. Since there is no DNS logic
+/// involved so the message sending between client and server is plain text
+/// And the valid checker, question lookup and answer composition are dummy.
+
+using namespace asiolink;
+using namespace asio;
+namespace {
+static const std::string server_ip = "127.0.0.1";
+const int server_port = 5553;
+//message client send to udp server, which isn't dns package
+//just for simple testing
+static const std::string query_message("BIND10 is awesome");
+
+// \brief provide capacity to derived class the ability
+// to stop DNSServer at certern point
+class ServerStopper {
+ public:
+ ServerStopper() : server_to_stop_(NULL) {}
+ virtual ~ServerStopper(){}
+
+ void setServerToStop(DNSServer* server) {
+ server_to_stop_ = server;
+ }
+
+ void stopServer() const {
+ if (server_to_stop_) {
+ server_to_stop_->stop();
+ }
+ }
+
+ private:
+ DNSServer* server_to_stop_;
+};
+
+// \brief no check logic at all,just provide a checkpoint to stop the server
+class DummyChecker : public SimpleCallback, public ServerStopper {
+ public:
+ virtual void operator()(const IOMessage&) const {
+ stopServer();
+ }
+};
+
+// \brief no lookup logic at all,just provide a checkpoint to stop the server
+class DummyLookup : public DNSLookup, public ServerStopper {
+ public:
+ void operator()(const IOMessage& io_message,
+ isc::dns::MessagePtr message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server) const {
+ stopServer();
+ server->resume(true);
+ }
+};
+
+// \brief copy the data received from user to the answer part
+// provide checkpoint to stop server
+class SimpleAnswer : public DNSAnswer, public ServerStopper {
+ public:
+ void operator()(const IOMessage& message,
+ isc::dns::MessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer) const
+ {
+ //copy what we get from user
+ buffer->writeData(message.getData(), message.getDataSize());
+ stopServer();
+ }
+
+};
+
+// \brief simple client, send one string to server and wait for response
+// in case, server stopped and client cann't get response, there is a timer wait
+// for specified seconds (the value is just a estimate since server process logic is quite
+// simple, and all the intercommunication is local) then cancel the waiting.
+class SimpleClient : public ServerStopper {
+ public:
+ static const size_t MAX_DATA_LEN = 256;
+ SimpleClient(asio::io_service& service,
+ unsigned int wait_server_time_out)
+ {
+ wait_for_response_timer_.reset(new deadline_timer(service));
+ received_data_ = new char[MAX_DATA_LEN];
+ received_data_len_ = 0;
+ wait_server_time_out_ = wait_server_time_out;
+ }
+
+ virtual ~SimpleClient() {
+ delete [] received_data_;
+ }
+
+ void setGetFeedbackCallback(boost::function<void()>& func) {
+ get_response_call_back_ = func;
+ }
+
+ virtual void sendDataThenWaitForFeedback(const std::string& data) = 0;
+ virtual std::string getReceivedData() const = 0;
+
+ void startTimer() {
+ wait_for_response_timer_->cancel();
+ wait_for_response_timer_->
+ expires_from_now(boost::posix_time::
+ seconds(wait_server_time_out_));
+ wait_for_response_timer_->
+ async_wait(boost::bind(&SimpleClient::stopWaitingforResponse,
+ this));
+ }
+
+ void cancelTimer() { wait_for_response_timer_->cancel(); }
+
+ void getResponseCallBack(const asio::error_code& error, size_t
+ received_bytes)
+ {
+ cancelTimer();
+ if (!error)
+ received_data_len_ = received_bytes;
+ if (!get_response_call_back_.empty()) {
+ get_response_call_back_();
+ }
+ stopServer();
+ }
+
+
+ protected:
+ virtual void stopWaitingforResponse() = 0;
+
+ boost::shared_ptr<deadline_timer> wait_for_response_timer_;
+ char* received_data_;
+ size_t received_data_len_;
+ boost::function<void()> get_response_call_back_;
+ unsigned int wait_server_time_out_;
+};
+
+
+
+class UDPClient : public SimpleClient {
+ public:
+ //After 1 seconds without feedback client will stop wait
+ static const unsigned int server_time_out = 1;
+
+ UDPClient(asio::io_service& service, const ip::udp::endpoint& server) :
+ SimpleClient(service, server_time_out)
+ {
+ server_ = server;
+ socket_.reset(new ip::udp::socket(service));
+ socket_->open(ip::udp::v4());
+ }
+
+
+ void sendDataThenWaitForFeedback(const std::string& data) {
+ received_data_len_ = 0;
+ socket_->send_to(buffer(data.c_str(), data.size() + 1), server_);
+ socket_->async_receive_from(buffer(received_data_, MAX_DATA_LEN),
+ received_from_,
+ boost::bind(&SimpleClient::
+ getResponseCallBack, this, _1,
+ _2));
+ startTimer();
+ }
+
+ virtual std::string getReceivedData() const {
+ return (received_data_len_ == 0 ? std::string("") :
+ std::string(received_data_));
+ }
+
+ private:
+ void stopWaitingforResponse() {
+ socket_->close();
+ }
+
+ boost::shared_ptr<ip::udp::socket> socket_;
+ ip::udp::endpoint server_;
+ ip::udp::endpoint received_from_;
+};
+
+
+class TCPClient : public SimpleClient {
+ public:
+ // after 2 seconds without feedback client will stop wait,
+ // this includes connect, send message and recevice message
+ static const unsigned int server_time_out = 2;
+ TCPClient(asio::io_service& service, const ip::tcp::endpoint& server)
+ : SimpleClient(service, server_time_out)
+ {
+ server_ = server;
+ socket_.reset(new ip::tcp::socket(service));
+ socket_->open(ip::tcp::v4());
+ }
+
+
+ virtual void sendDataThenWaitForFeedback(const std::string &data) {
+ received_data_len_ = 0;
+ data_to_send_ = data;
+ data_to_send_len_ = data.size() + 1;
+ socket_->async_connect(server_, boost::bind(&TCPClient::connectHandler,
+ this, _1));
+ startTimer();
+ }
+
+ virtual std::string getReceivedData() const {
+ return (received_data_len_ == 0 ? std::string("") :
+ std::string(received_data_ + 2));
+ }
+
+ private:
+ void stopWaitingforResponse() {
+ socket_->close();
+ }
+
+ void connectHandler(const asio::error_code& error) {
+ if (!error) {
+ data_to_send_len_ = htons(data_to_send_len_);
+ socket_->async_send(buffer(&data_to_send_len_, 2),
+ boost::bind(&TCPClient::sendMessageBodyHandler,
+ this, _1, _2));
+ }
+ }
+
+ void sendMessageBodyHandler(const asio::error_code& error,
+ size_t send_bytes)
+ {
+ if (!error && send_bytes == 2) {
+ socket_->async_send(buffer(data_to_send_.c_str(),
+ data_to_send_.size() + 1),
+ boost::bind(&TCPClient::finishSendHandler, this, _1, _2));
+ }
+ }
+
+ void finishSendHandler(const asio::error_code& error, size_t send_bytes) {
+ if (!error && send_bytes == data_to_send_.size() + 1) {
+ socket_->async_receive(buffer(received_data_, MAX_DATA_LEN),
+ boost::bind(&SimpleClient::getResponseCallBack, this, _1,
+ _2));
+ }
+ }
+
+ boost::shared_ptr<ip::tcp::socket> socket_;
+ ip::tcp::endpoint server_;
+ std::string data_to_send_;
+ uint16_t data_to_send_len_;
+};
+
+
+
+// \brief provide the context which including two client and
+// two server, udp client will only communicate with udp server, same for tcp client
+class DNSServerTest : public::testing::Test {
+ protected:
+ void SetUp() {
+ ip::address server_address = ip::address::from_string(server_ip);
+ checker_ = new DummyChecker();
+ lookup_ = new DummyLookup();
+ answer_ = new SimpleAnswer();
+ udp_server_ = new UDPServer(service, server_address, server_port,
+ checker_, lookup_, answer_);
+ udp_client_ = new UDPClient(service,
+ ip::udp::endpoint(server_address,
+ server_port));
+ tcp_server_ = new TCPServer(service, server_address, server_port,
+ checker_, lookup_, answer_);
+ tcp_client_ = new TCPClient(service,
+ ip::tcp::endpoint(server_address,
+ server_port));
+ }
+
+
+ void TearDown() {
+ udp_server_->stop();
+ tcp_server_->stop();
+ delete checker_;
+ delete lookup_;
+ delete answer_;
+ delete udp_server_;
+ delete udp_client_;
+ delete tcp_server_;
+ delete tcp_client_;
+ }
+
+
+ void testStopServerByStopper(DNSServer* server, SimpleClient* client,
+ ServerStopper* stopper)
+ {
+ static const unsigned int io_service_time_out = 5;
+ io_service_is_time_out = false;
+ stopper->setServerToStop(server);
+ (*server)();
+ client->sendDataThenWaitForFeedback(query_message);
+ // Since thread hasn't been introduced into the tool box, using signal
+ // to make sure run function will eventually return even server stop
+ // failed
+ void (*prev_handler)(int) = std::signal(SIGALRM, DNSServerTest::stopIOService);
+ alarm(io_service_time_out);
+ service.run();
+ service.reset();
+ //cancel scheduled alarm
+ alarm(0);
+ std::signal(SIGALRM, prev_handler);
+ }
+
+
+ static void stopIOService(int _no_use_parameter) {
+ io_service_is_time_out = true;
+ service.stop();
+ }
+
+ bool serverStopSucceed() const {
+ return (!io_service_is_time_out);
+ }
+
+ DummyChecker* checker_;
+ DummyLookup* lookup_;
+ SimpleAnswer* answer_;
+ UDPServer* udp_server_;
+ UDPClient* udp_client_;
+ TCPClient* tcp_client_;
+ TCPServer* tcp_server_;
+
+ // To access them in signal handle function, the following
+ // variables have to be static.
+ static asio::io_service service;
+ static bool io_service_is_time_out;
+};
+
+bool DNSServerTest::io_service_is_time_out = false;
+asio::io_service DNSServerTest::service;
+
+// Test whether server stopped successfully after client get response
+// client will send query and start to wait for response, once client
+// get response, udp server will be stopped, the io service won't quit
+// if udp server doesn't stop successfully.
+TEST_F(DNSServerTest, stopUDPServerAfterOneQuery) {
+ testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+ EXPECT_EQ(query_message, udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+// Test whether udp server stopped successfully before server start to serve
+TEST_F(DNSServerTest, stopUDPServerBeforeItStartServing) {
+ udp_server_->stop();
+ testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+
+// Test whether udp server stopped successfully during message check
+TEST_F(DNSServerTest, stopUDPServerDuringMessageCheck) {
+ testStopServerByStopper(udp_server_, udp_client_, checker_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+// Test whether udp server stopped successfully during query lookup
+TEST_F(DNSServerTest, stopUDPServerDuringQueryLookup) {
+ testStopServerByStopper(udp_server_, udp_client_, lookup_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+// Test whether udp server stopped successfully during composing answer
+TEST_F(DNSServerTest, stopUDPServerDuringPrepareAnswer) {
+ testStopServerByStopper(udp_server_, udp_client_, answer_);
+ EXPECT_EQ(std::string(""), udp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+static void stopServerManyTimes(DNSServer *server, unsigned int times) {
+ for (int i = 0; i < times; ++i) {
+ server->stop();
+ }
+}
+
+// Test whether udp server stop interface can be invoked several times without
+// throw any exception
+TEST_F(DNSServerTest, stopUDPServeMoreThanOnce) {
+ ASSERT_NO_THROW({
+ boost::function<void()> stop_server_3_times
+ = boost::bind(stopServerManyTimes, udp_server_, 3);
+ udp_client_->setGetFeedbackCallback(stop_server_3_times);
+ testStopServerByStopper(udp_server_, udp_client_, udp_client_);
+ EXPECT_EQ(query_message, udp_client_->getReceivedData());
+ });
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+
+TEST_F(DNSServerTest, stopTCPServerAfterOneQuery) {
+ testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+ EXPECT_EQ(query_message, tcp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+
+// Test whether tcp server stopped successfully before server start to serve
+TEST_F(DNSServerTest, stopTCPServerBeforeItStartServing) {
+ tcp_server_->stop();
+ testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+ EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+
+// Test whether tcp server stopped successfully during message check
+TEST_F(DNSServerTest, stopTCPServerDuringMessageCheck) {
+ testStopServerByStopper(tcp_server_, tcp_client_, checker_);
+ EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+// Test whether tcp server stopped successfully during query lookup
+TEST_F(DNSServerTest, stopTCPServerDuringQueryLookup) {
+ testStopServerByStopper(tcp_server_, tcp_client_, lookup_);
+ EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+// Test whether tcp server stopped successfully during composing answer
+TEST_F(DNSServerTest, stopTCPServerDuringPrepareAnswer) {
+ testStopServerByStopper(tcp_server_, tcp_client_, answer_);
+ EXPECT_EQ(std::string(""), tcp_client_->getReceivedData());
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+
+// Test whether tcp server stop interface can be invoked several times without
+// throw any exception
+TEST_F(DNSServerTest, stopTCPServeMoreThanOnce) {
+ ASSERT_NO_THROW({
+ boost::function<void()> stop_server_3_times
+ = boost::bind(stopServerManyTimes, tcp_server_, 3);
+ tcp_client_->setGetFeedbackCallback(stop_server_3_times);
+ testStopServerByStopper(tcp_server_, tcp_client_, tcp_client_);
+ EXPECT_EQ(query_message, tcp_client_->getReceivedData());
+ });
+ EXPECT_TRUE(serverStopSucceed());
+}
+
+}
diff --git a/src/lib/asiolink/tests/io_endpoint_unittest.cc b/src/lib/asiolink/tests/io_endpoint_unittest.cc
index 6101473..5170f7d 100644
--- a/src/lib/asiolink/tests/io_endpoint_unittest.cc
+++ b/src/lib/asiolink/tests/io_endpoint_unittest.cc
@@ -60,6 +60,62 @@ TEST(IOEndpointTest, createTCPv6) {
EXPECT_EQ(IPPROTO_TCP, ep->getProtocol());
}
+TEST(IOEndpointTest, equality) {
+ std::vector<const IOEndpoint *> epv;
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5303));
+ epv.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5304));
+ epv.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5304));
+
+ for (size_t i = 0; i < epv.size(); ++i) {
+ for (size_t j = 0; j < epv.size(); ++j) {
+ if (i != j) {
+ // We use EXPECT_TRUE/FALSE instead of _EQ here, since
+ // _EQ requires there is an operator<< as well
+ EXPECT_FALSE(*epv[i] == *epv[j]);
+ EXPECT_TRUE(*epv[i] != *epv[j]);
+ }
+ }
+ }
+
+ // Create a second array with exactly the same values. We use create()
+ // again to make sure we get different endpoints
+ std::vector<const IOEndpoint *> epv2;
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1234"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1234"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("2001:db8::1235"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("2001:db8::1235"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.1"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.1"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5303));
+ epv2.push_back(IOEndpoint::create(IPPROTO_TCP, IOAddress("192.0.2.2"), 5304));
+ epv2.push_back(IOEndpoint::create(IPPROTO_UDP, IOAddress("192.0.2.2"), 5304));
+
+ for (size_t i = 0; i < epv.size(); ++i) {
+ EXPECT_TRUE(*epv[i] == *epv2[i]);
+ EXPECT_FALSE(*epv[i] != *epv2[i]);
+ }
+}
+
TEST(IOEndpointTest, createIPProto) {
EXPECT_THROW(IOEndpoint::create(IPPROTO_IP, IOAddress("192.0.2.1"),
53210)->getAddress().toText(),
diff --git a/src/lib/asiolink/tests/io_fetch_unittest.cc b/src/lib/asiolink/tests/io_fetch_unittest.cc
index 832961d..2b258b8 100644
--- a/src/lib/asiolink/tests/io_fetch_unittest.cc
+++ b/src/lib/asiolink/tests/io_fetch_unittest.cc
@@ -12,13 +12,17 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-#include <gtest/gtest.h>
-#include <boost/bind.hpp>
+#include <algorithm>
#include <cstdlib>
#include <string>
+#include <iostream>
+#include <iomanip>
+#include <iterator>
#include <vector>
-#include <string.h>
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <asio.hpp>
@@ -30,19 +34,27 @@
#include <dns/name.h>
#include <dns/rcode.h>
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
#include <asiolink/io_fetch.h>
#include <asiolink/io_service.h>
using namespace asio;
using namespace isc::dns;
-using asio::ip::udp;
+using namespace asio::ip;
+using namespace std;
namespace asiolink {
const asio::ip::address TEST_HOST(asio::ip::address::from_string("127.0.0.1"));
const uint16_t TEST_PORT(5301);
-// FIXME Shouldn't we send something that is real message?
-const char TEST_DATA[] = "TEST DATA";
+const int SEND_INTERVAL = 250; // Interval in ms between TCP sends
+const size_t MAX_SIZE = 64 * 1024; // Should be able to take 64kB
+
+// The tests are complex, so debug output has been left in (although disabled).
+// Set this to true to enable it.
+const bool DEBUG = false;
/// \brief Test fixture for the asiolink::IOFetch.
class IOFetchTest : public virtual ::testing::Test, public virtual IOFetch::Callback
@@ -52,14 +64,32 @@ public:
IOFetch::Result expected_; ///< Expected result of the callback
bool run_; ///< Did the callback run already?
Question question_; ///< What to ask
- OutputBufferPtr buff_; ///< Buffer to hold result
+ OutputBufferPtr result_buff_; ///< Buffer to hold result of fetch
+ OutputBufferPtr msgbuf_; ///< Buffer corresponding to known question
IOFetch udp_fetch_; ///< For UDP query test
- //IOFetch tcp_fetch_; ///< For TCP query test
-
- // The next member is the buffer iin which the "server" (implemented by the
- // response handler method) receives the question sent by the fetch object.
- std::vector<char> server_buff_; ///< Server buffer
- OutputBufferPtr expected_buff_; ///< Data we expect to receive
+ IOFetch tcp_fetch_; ///< For TCP query test
+ IOFetch::Protocol protocol_; ///< Protocol being tested
+ size_t cumulative_; ///< Cumulative data received by "server".
+ deadline_timer timer_; ///< Timer to measure timeouts
+
+ // The next member is the buffer in which the "server" (implemented by the
+ // response handler methods in this class) receives the question sent by the
+ // fetch object.
+ uint8_t receive_buffer_[MAX_SIZE]; ///< Server receive buffer
+ OutputBufferPtr expected_buffer_; ///< Data we expect to receive
+ vector<uint8_t> send_buffer_; ///< Server send buffer
+ uint16_t send_cumulative_; ///< Data sent so far
+
+ // Other data.
+ string return_data_; ///< Data returned by server
+ string test_data_; ///< Large string - here for convenience
+ bool debug_; ///< true to enable debug output
+ size_t tcp_send_size_; ///< Max size of TCP send
+ uint8_t qid_0; ///< First octet of qid
+ uint8_t qid_1; ///< Second octet of qid
+
+ bool tcp_short_send_; ///< If set to true, we do not send
+ /// all data in the tcp response
/// \brief Constructor
IOFetchTest() :
@@ -67,193 +97,626 @@ public:
expected_(IOFetch::NOTSET),
run_(false),
question_(Name("example.net"), RRClass::IN(), RRType::A()),
- buff_(new OutputBuffer(512)),
- udp_fetch_(IPPROTO_UDP, service_, question_, IOAddress(TEST_HOST),
- TEST_PORT, buff_, this, 100),
- server_buff_(512), expected_buff_(new OutputBuffer(512))
- // tcp_fetch_(service_, question_, IOAddress(TEST_HOST), TEST_PORT,
- // buff_, this, 100, IPPROTO_UDP)
- { }
+ result_buff_(new OutputBuffer(512)),
+ msgbuf_(new OutputBuffer(512)),
+ udp_fetch_(IOFetch::UDP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, 100),
+ tcp_fetch_(IOFetch::TCP, service_, question_, IOAddress(TEST_HOST),
+ TEST_PORT, result_buff_, this, (16 * SEND_INTERVAL)),
+ // Timeout interval chosen to ensure no timeout
+ protocol_(IOFetch::TCP), // for initialization - will be changed
+ cumulative_(0),
+ timer_(service_.get_io_service()),
+ receive_buffer_(),
+ expected_buffer_(new OutputBuffer(512)),
+ send_buffer_(),
+ send_cumulative_(0),
+ return_data_(""),
+ test_data_(""),
+ debug_(DEBUG),
+ tcp_send_size_(0),
+ qid_0(0),
+ qid_1(0),
+ tcp_short_send_(false)
+ {
+ // Construct the data buffer for question we expect to receive.
+ Message msg(Message::RENDER);
+ msg.setQid(0);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setRcode(Rcode::NOERROR());
+ msg.setHeaderFlag(Message::HEADERFLAG_RD);
+ msg.addQuestion(question_);
+ MessageRenderer renderer(*msgbuf_);
+ msg.toWire(renderer);
+ MessageRenderer renderer2(*expected_buffer_);
+ msg.toWire(renderer2);
+
+ // Initialize the test data to be returned: tests will return a
+ // substring of this data. (It's convenient to have this as a member of
+ // the class.)
+ //
+ // We could initialize the data with a single character, but as an added
+ // check we'll make ssre that it has some structure.
+
+ test_data_.clear();
+ test_data_.reserve(MAX_SIZE);
+ while (test_data_.size() < MAX_SIZE) {
+ test_data_ += "A message to be returned to the client that has "
+ "some sort of structure.";
+ }
+ }
+
+ /// \brief UDP Response handler (the "remote UDP DNS server")
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It checks that the data sent by the IOFetch object is what
+ /// was expected to have been sent, then sends back a known buffer of data.
+ ///
+ /// \param remote Endpoint to which to send the answer
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param bad_qid If set to true, the QID in the response will be mangled
+ /// \param second_send If set to true, (and bad_qid is too), after the
+ /// mangled qid response has been sent, a second packet will be
+ /// sent with the correct QID.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(udp::endpoint* remote, udp::socket* socket,
+ error_code ec = error_code(), size_t length = 0,
+ bool bad_qid = false, bool second_send = false) {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ qid_0 = receive_buffer_[0];
+ qid_1 = receive_buffer_[1];
+ receive_buffer_[0] = receive_buffer_[1] = 0;
+
+ // Check that length of the received data and the expected data are
+ // identical, then check that the data is identical as well.
+ EXPECT_EQ(msgbuf_->getLength(), length);
+ EXPECT_TRUE(equal(receive_buffer_, (receive_buffer_ + length - 1),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // Return a message back to the IOFetch object.
+ if (!bad_qid) {
+ expected_buffer_->writeUint8At(qid_0, 0);
+ expected_buffer_->writeUint8At(qid_1, 1);
+ } else {
+ expected_buffer_->writeUint8At(qid_0 + 1, 0);
+ expected_buffer_->writeUint8At(qid_1 + 1, 1);
+ }
+ socket->send_to(asio::buffer(expected_buffer_->getData(), length), *remote);
+
+ if (bad_qid && second_send) {
+ expected_buffer_->writeUint8At(qid_0, 0);
+ expected_buffer_->writeUint8At(qid_1, 1);
+ socket->send_to(asio::buffer(expected_buffer_->getData(),
+ expected_buffer_->getLength()), *remote);
+ }
+ if (debug_) {
+ cout << "udpReceiveHandler(): returned " << expected_buffer_->getLength() <<
+ " bytes to the client" << endl;
+ }
+ }
+
+ /// \brief Completion Handler for accepting TCP data
+ ///
+ /// Called when the remote system connects to the "server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(tcp::socket* socket, error_code ec = error_code())
+ {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Work out the maximum size of data we can send over it when we
+ // respond, then subtract 1kB or so for safety.
+ tcp::socket::send_buffer_size send_size;
+ socket->get_option(send_size);
+ if (send_size.value() < (2 * 1024)) {
+ FAIL() << "TCP send size is less than 2kB";
+ } else {
+ tcp_send_size_ = send_size.value() - 1024;
+ if (debug_) {
+ cout << "tcpacceptHandler(): will use send size = " << tcp_send_size_ << endl;
+ }
+ }
+
+ // Initiate a read on the socket.
+ cumulative_ = 0;
+ socket->async_receive(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2));
+ }
+
+ /// \brief Completion handler for receiving TCP data
+ ///
+ /// When IOFetch is sending data, this response handler emulates the remote
+ /// DNS server. It that all the data sent by the IOFetch object has been
+ /// received, issuing another read if not. If the data is complete, it is
+ /// compared to what is expected and a reply sent back to the IOFetch.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(tcp::socket* socket, error_code ec = error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // If we haven't received all the data, issue another read.
+ cumulative_ += length;
+ bool complete = false;
+ if (cumulative_ > 2) {
+ uint16_t dns_length = readUint16(receive_buffer_);
+ complete = ((dns_length + 2) == cumulative_);
+ }
+
+ if (!complete) {
+ socket->async_receive(asio::buffer((receive_buffer_ + cumulative_),
+ (sizeof(receive_buffer_) - cumulative_)),
+ boost::bind(&IOFetchTest::tcpReceiveHandler, this, socket, _1, _2));
+ return;
+ }
+
+ // Check that length of the DNS message received is that expected, then
+ // compare buffers, zeroing the QID in the received buffer to match
+ // that set in our expected question. Note that due to the length
+ // field the QID in the received buffer is in the third and fourth
+ // bytes.
+ EXPECT_EQ(msgbuf_->getLength() + 2, cumulative_);
+ qid_0 = receive_buffer_[2];
+ qid_1 = receive_buffer_[3];
+
+ receive_buffer_[2] = receive_buffer_[3] = 0;
+ EXPECT_TRUE(equal((receive_buffer_ + 2), (receive_buffer_ + cumulative_ - 2),
+ static_cast<const uint8_t*>(msgbuf_->getData())));
+
+ // ... and return a message back. This has to be preceded by a two-byte
+ // count field.
+
+ send_buffer_.clear();
+ send_buffer_.push_back(0);
+ send_buffer_.push_back(0);
+ writeUint16(return_data_.size(), &send_buffer_[0]);
+ copy(return_data_.begin(), return_data_.end(), back_inserter(send_buffer_));
+ if (return_data_.size() >= 2) {
+ send_buffer_[2] = qid_0;
+ send_buffer_[3] = qid_1;
+ }
+ // Send the data. This is done in multiple writes with a delay between
+ // each to check that the reassembly of TCP packets from fragments works.
+ send_cumulative_ = 0;
+ tcpSendData(socket);
+ }
+
+ /// \brief Sent Data Over TCP
+ ///
+ /// Send the TCP data back to the IOFetch object. The data is sent in
+ /// three chunks - two of 16 bytes and the remainder, with a 250ms gap
+ /// between each. (Amounts of data smaller than one 32 bytes are sent in
+ /// one or two packets.)
+ ///
+ /// \param socket Socket over which send should take place
+ void tcpSendData(tcp::socket* socket) {
+ if (debug_) {
+ cout << "tcpSendData()" << endl;
+ }
+
+ // Decide what to send based on the cumulative count. At most we'll do
+ // two chunks of 16 bytes (with a 250ms gap between) and then the
+ // remainder.
+ uint8_t* send_ptr = &send_buffer_[send_cumulative_];
+ // Pointer to data to send
+ size_t amount = 16; // Amount of data to send
+ if (send_cumulative_ < (2 * amount)) {
+
+ // First or second time through, send at most 16 bytes
+ amount = min(amount, (send_buffer_.size() - send_cumulative_));
+
+ } else {
+
+ // For all subsequent times, send the remainder, maximised to
+ // whatever we have chosen for the maximum send size.
+ amount = min(tcp_send_size_,
+ (send_buffer_.size() - send_cumulative_));
+ }
+
+ // This is for the short send test; reduce the actual amount of
+ // data we send
+ if (tcp_short_send_) {
+ if (debug_) {
+ cout << "tcpSendData(): sending incomplete data (" <<
+ (amount - 1) << " of " << amount << " bytes)" <<
+ endl;
+ }
+ --amount;
+ } else {
+ if (debug_) {
+ cout << "tcpSendData(): sending " << amount << " bytes" << endl;
+ }
+ }
+
+ // ... and send it. The amount sent is also passed as the first
+ // argument of the send callback, as a check.
+ socket->async_send(asio::buffer(send_ptr, amount),
+ boost::bind(&IOFetchTest::tcpSendHandler, this,
+ amount, socket, _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the IOFetch object
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// If not all the data has been sent, a short delay is instigated (during
+ /// which control returns to the IOService). This should force the queued
+ /// data to actually be sent and the IOFetch receive handler to be triggered.
+ /// In this way, the ability of IOFetch to handle fragmented TCP packets
+ /// should be checked.
+ ///
+ /// \param expected Number of bytes that were expected to have been sent.
+ /// \param socket Socket over which the send took place. Only used to
+ /// pass back to the send method.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected, tcp::socket* socket,
+ error_code ec = error_code(), size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected, length); // And that amount sent is as expected
+
+ // Do we need to send more?
+ send_cumulative_ += length;
+ if (send_cumulative_ < send_buffer_.size()) {
+
+ // Yes - set up a timer: the callback handler for the timer is
+ // tcpSendData, which will then send the next chunk. We pass the
+ // socket over which data should be sent as an argument to that
+ // function.
+ timer_.expires_from_now(boost::posix_time::milliseconds(SEND_INTERVAL));
+ timer_.async_wait(boost::bind(&IOFetchTest::tcpSendData, this,
+ socket));
+ }
+ }
/// \brief Fetch completion callback
///
/// This is the callback's operator() method which is called when the fetch
- /// is complete. Check that the data received is the wire format of the
- /// question, then send back an arbitrary response.
+ /// is complete. It checks that the data received is the wire format of the
+ /// data sent back by the server.
+ ///
+ /// \param result Result indicated by the callback
void operator()(IOFetch::Result result) {
+ if (debug_) {
+ cout << "operator()(): result = " << result << endl;
+ }
+
EXPECT_EQ(expected_, result); // Check correct result returned
EXPECT_FALSE(run_); // Check it is run only once
run_ = true; // Note success
- service_.stop(); // ... and exit run loop
+
+ // If the expected result for SUCCESS, then this should have been called
+ // when one of the "servers" in this class has sent back return_data_.
+ // Check the data is as expected/
+ if (expected_ == IOFetch::SUCCESS) {
+ // In the case of UDP, we actually send back a real looking packet
+ // in the case of TCP, we send back a 'random' string
+ if (protocol_ == IOFetch::UDP) {
+ EXPECT_EQ(expected_buffer_->getLength(), result_buff_->getLength());
+ EXPECT_EQ(0, memcmp(expected_buffer_->getData(), result_buff_->getData(),
+ expected_buffer_->getLength()));
+ } else {
+ EXPECT_EQ(return_data_.size(), result_buff_->getLength());
+ // Overwrite the random qid with our own data for the
+ // comparison to succeed
+ if (result_buff_->getLength() >= 2) {
+ result_buff_->writeUint8At(return_data_[0], 0);
+ result_buff_->writeUint8At(return_data_[1], 1);
+ }
+ const uint8_t* start = static_cast<const uint8_t*>(result_buff_->getData());
+ EXPECT_TRUE(equal(return_data_.begin(), return_data_.end(), start));
+ }
+ }
+
+ // ... and cause the run loop to exit.
+ service_.stop();
}
+ // The next set of methods are the tests themselves. A number of the TCP
+ // and UDP tests are very similar.
- /// \brief Response handler, pretending to be remote DNS server
+ /// \brief Check for stop()
///
- /// This checks that the data sent is what we expected to receive, and
- /// sends back a test answer.
+ /// Test that when we run the query and stop it after it was run, it returns
+ /// "stopped" correctly. (That is why stop() is posted to the service_ as
+ /// well instead of calling it.)
///
- /// If bad_qid is true, mangle the qid in the response
- /// If second_send is true (as well as bad_qid), send the packet
- /// again, but with the right qid this time
- void respond(udp::endpoint* remote, udp::socket* socket,
- asio::error_code ec = asio::error_code(), size_t length = 0,
- bool bad_qid = false, bool second_send = false) {
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void stopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Post the query
+ service_.get_io_service().post(fetch);
+
+ // Post query_.stop() (yes, the boost::bind thing is just
+ // query_.stop()).
+ service_.get_io_service().post(
+ boost::bind(&IOFetch::stop, fetch, IOFetch::STOPPED));
+
+ // Run both of them. run() returns when everything in the I/O service
+ // queue has completed.
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
- // Construct the data buffer for question we expect to receive.
- Message msg(Message::RENDER);
- msg.setQid(0);
- msg.setOpcode(Opcode::QUERY());
- msg.setRcode(Rcode::NOERROR());
- msg.setHeaderFlag(Message::HEADERFLAG_RD);
- msg.addQuestion(question_);
- MessageRenderer renderer(*expected_buff_);
- msg.toWire(renderer);
+ /// \brief Premature stop test
+ ///
+ /// Test that when we queue the query to service_ and call stop() before it
+ /// gets executed, it acts sanely as well (eg. has the same result as
+ /// running stop() after - calls the callback).
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void prematureStopTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::STOPPED;
+
+ // Stop before it is started
+ fetch.stop();
+ service_.get_io_service().post(fetch);
+
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
- // The QID in the incoming data is random so set it to 0 for the
- // data comparison check. (It was set to 0 when the buffer containing
- // the expected data was constructed above.)
- // We do store the first 2 octets for qid checks
- uint8_t octet0 = server_buff_[0];
- uint8_t octet1 = server_buff_[1];
- server_buff_[0] = 0;
- server_buff_[1] = 0;
-
- // Check that lengths are identical.
- EXPECT_EQ(expected_buff_->getLength(), length);
- EXPECT_TRUE(memcmp(expected_buff_->getData(), &server_buff_[0], length) == 0);
-
- // ... and return a message back. (just echo what we read)
- if (!bad_qid) {
- expected_buff_->writeUint8At(octet0, 0);
- expected_buff_->writeUint8At(octet1, 1);
+ /// \brief Timeout test
+ ///
+ /// Test that fetch times out when no answer arrives.
+ ///
+ /// \param protocol Test protocol
+ /// \param fetch Fetch object being tested
+ void timeoutTest(IOFetch::Protocol protocol, IOFetch& fetch) {
+ protocol_ = protocol;
+ expected_ = IOFetch::TIME_OUT;
+
+ service_.get_io_service().post(fetch);
+ service_.run();
+ EXPECT_TRUE(run_);
+ }
+
+ /// \brief Send/Receive Test
+ ///
+ /// Send a query to the server then receives a response.
+ ///
+ /// \param Test data to return to client
+ /// \param short_send If true, do not send all data
+ /// (should result in timeout)
+ void tcpSendReturnTest(const std::string& return_data, bool short_send = false) {
+ if (debug_) {
+ cout << "tcpSendReturnTest(): data size = " << return_data.size() << endl;
+ }
+ return_data_ = return_data;
+ protocol_ = IOFetch::TCP;
+ if (short_send) {
+ tcp_short_send_ = true;
+ expected_ = IOFetch::TIME_OUT;
} else {
- expected_buff_->writeUint8At(octet0 + 1, 0);
- expected_buff_->writeUint8At(octet1 + 1, 1);
+ expected_ = IOFetch::SUCCESS;
}
- socket->send_to(asio::buffer(expected_buff_->getData(), length), *remote);
- if (bad_qid && second_send) {
- expected_buff_->writeUint8At(octet0, 0);
- expected_buff_->writeUint8At(octet1, 1);
- socket->send_to(asio::buffer(expected_buff_->getData(), length), *remote);
+ // Socket into which the connection will be accepted.
+ tcp::socket socket(service_.get_io_service());
+
+ // Acceptor object - called when the connection is made, the handler
+ // will initiate a read on the socket.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(socket,
+ boost::bind(&IOFetchTest::tcpAcceptHandler, this, &socket, _1));
+
+ // Post the TCP fetch object to send the query and receive the response.
+ service_.get_io_service().post(tcp_fetch_);
+
+ // ... and execute all the callbacks. This exits when the fetch
+ // completes.
+ service_.run();
+ EXPECT_TRUE(run_); // Make sure the callback did execute
+
+ // Tidy up
+ socket.close();
+ }
+
+ /// Perform a send/receive test over UDP
+ ///
+ /// \param bad_qid If true, do the test where the QID is mangled
+ /// in the response
+ /// \param second_send If true, do the test where the QID is
+ /// mangled in the response, but a second
+ /// (correct) packet is used
+ void udpSendReturnTest(bool bad_qid, bool second_send) {
+ protocol_ = IOFetch::UDP;
+
+ // Set up the server.
+ udp::socket socket(service_.get_io_service(), udp::v4());
+ socket.set_option(socket_base::reuse_address(true));
+ socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+ return_data_ = "Message returned to the client";
+
+ udp::endpoint remote;
+ socket.async_receive_from(asio::buffer(receive_buffer_, sizeof(receive_buffer_)),
+ remote,
+ boost::bind(&IOFetchTest::udpReceiveHandler, this, &remote, &socket,
+ _1, _2, bad_qid, second_send));
+ service_.get_io_service().post(udp_fetch_);
+ if (debug_) {
+ cout << "udpSendReceive: async_receive_from posted, waiting for callback" <<
+ endl;
}
+ service_.run();
+
+ socket.close();
+
+ EXPECT_TRUE(run_);;
}
};
+// Check the protocol
+TEST_F(IOFetchTest, Protocol) {
+ EXPECT_EQ(IOFetch::UDP, udp_fetch_.getProtocol());
+ EXPECT_EQ(IOFetch::TCP, tcp_fetch_.getProtocol());
+}
-/// Test that when we run the query and stop it after it was run,
-/// it returns "stopped" correctly.
-///
-/// That is why stop() is posted to the service_ as well instead
-/// of calling it.
+// UDP Stop test - see IOFetchTest::stopTest() header.
TEST_F(IOFetchTest, UdpStop) {
- expected_ = IOFetch::STOPPED;
-
- // Post the query
- service_.get_io_service().post(udp_fetch_);
+ stopTest(IOFetch::UDP, udp_fetch_);
+}
- // Post query_.stop() (yes, the boost::bind thing is just
- // query_.stop()).
- service_.get_io_service().post(
- boost::bind(&IOFetch::stop, udp_fetch_, IOFetch::STOPPED));
+// UDP premature stop test - see IOFetchTest::prematureStopTest() header.
+TEST_F(IOFetchTest, UdpPrematureStop) {
+ prematureStopTest(IOFetch::UDP, udp_fetch_);
+}
- // Run both of them. run() returns when everything in the I/O service
- // queue has completed.
- service_.run();
- EXPECT_TRUE(run_);
+// UDP premature stop test - see IOFetchTest::timeoutTest() header.
+TEST_F(IOFetchTest, UdpTimeout) {
+ timeoutTest(IOFetch::UDP, udp_fetch_);
}
-// Test that when we queue the query to service_ and call stop() before it gets
-// executed, it acts sanely as well (eg. has the same result as running stop()
-// after - calls the callback).
-TEST_F(IOFetchTest, UdpPrematureStop) {
- expected_ = IOFetch::STOPPED;
+// UDP SendReceive test. Set up a UDP server then ports a UDP fetch object.
+// This will send question_ to the server and receive the answer back from it.
+TEST_F(IOFetchTest, UdpSendReceive) {
+ expected_ = IOFetch::SUCCESS;
- // Stop before it is started
- udp_fetch_.stop();
- service_.get_io_service().post(udp_fetch_);
+ udpSendReturnTest(false, false);
- service_.run();
- EXPECT_TRUE(run_);
+ EXPECT_TRUE(run_);;
}
-// Test that it will timeout when no answer arrives.
-TEST_F(IOFetchTest, UdpTimeout) {
+TEST_F(IOFetchTest, UdpSendReceiveBadQid) {
expected_ = IOFetch::TIME_OUT;
- service_.get_io_service().post(udp_fetch_);
- service_.run();
- EXPECT_TRUE(run_);
+ udpSendReturnTest(true, false);
+
+ EXPECT_TRUE(run_);;
}
-// Test that it will succeed when we fake an answer and stores the same data we
-// send. This is done through a real socket on the loopback address.
-TEST_F(IOFetchTest, UdpReceive) {
+TEST_F(IOFetchTest, UdpSendReceiveBadQidResend) {
expected_ = IOFetch::SUCCESS;
- udp::socket socket(service_.get_io_service(), udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+ udpSendReturnTest(true, true);
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(server_buff_),
- remote,
- boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2, false, false));
- service_.get_io_service().post(udp_fetch_);
- service_.run();
+ EXPECT_TRUE(run_);;
+}
- socket.close();
+// Do the same tests for TCP transport
- EXPECT_TRUE(run_);
- ASSERT_EQ(expected_buff_->getLength(), buff_->getLength());
- EXPECT_EQ(0, memcmp(expected_buff_->getData(), buff_->getData(),
- expected_buff_->getLength()));
+TEST_F(IOFetchTest, TcpStop) {
+ stopTest(IOFetch::TCP, tcp_fetch_);
}
-// Test that it will ignore any response that has the wrong QID
-TEST_F(IOFetchTest, UdpReceiveBadQid) {
- expected_ = IOFetch::TIME_OUT;
+TEST_F(IOFetchTest, TcpPrematureStop) {
+ prematureStopTest(IOFetch::TCP, tcp_fetch_);
+}
- udp::socket socket(service_.get_io_service(), udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+TEST_F(IOFetchTest, TcpTimeout) {
+ timeoutTest(IOFetch::TCP, tcp_fetch_);
+}
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(server_buff_),
- remote,
- boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2, true, false));
- service_.get_io_service().post(udp_fetch_);
- service_.run();
+// Test with values at or near 2, then at or near the chunk size (16 and 32
+// bytes, the sizes of the first two packets) then up to 65535. These are done
+// in separate tests because in practice a new IOFetch is created for each
+// query/response exchange and we don't want to confuse matters in the test
+// by running the test with an IOFetch that has already done one exchange.
+//
+// Don't do 0 or 1; the server would not accept the packet
+// (since the length is too short to check the qid)
+TEST_F(IOFetchTest, TcpSendReceive2) {
+ tcpSendReturnTest(test_data_.substr(0, 2));
+}
- socket.close();
+TEST_F(IOFetchTest, TcpSendReceive3) {
+ tcpSendReturnTest(test_data_.substr(0, 3));
+}
- EXPECT_TRUE(run_);
- ASSERT_EQ(0, buff_->getLength());
+TEST_F(IOFetchTest, TcpSendReceive15) {
+ tcpSendReturnTest(test_data_.substr(0, 15));
}
-// Test that it will ignore any response that has the wrong QID, but
-// accept the one with the right QID that comes next
-TEST_F(IOFetchTest, UdpReceiveBadQid2) {
- expected_ = IOFetch::SUCCESS;
+TEST_F(IOFetchTest, TcpSendReceive16) {
+ tcpSendReturnTest(test_data_.substr(0, 16));
+}
- udp::socket socket(service_.get_io_service(), udp::v4());
- socket.set_option(socket_base::reuse_address(true));
- socket.bind(udp::endpoint(TEST_HOST, TEST_PORT));
+TEST_F(IOFetchTest, TcpSendReceive17) {
+ tcpSendReturnTest(test_data_.substr(0, 17));
+}
- udp::endpoint remote;
- socket.async_receive_from(asio::buffer(server_buff_),
- remote,
- boost::bind(&IOFetchTest::respond, this, &remote, &socket, _1, _2, true, true));
- service_.get_io_service().post(udp_fetch_);
- service_.run();
+TEST_F(IOFetchTest, TcpSendReceive31) {
+ tcpSendReturnTest(test_data_.substr(0, 31));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32) {
+ tcpSendReturnTest(test_data_.substr(0, 32));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive33) {
+ tcpSendReturnTest(test_data_.substr(0, 33));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive4096) {
+ tcpSendReturnTest(test_data_.substr(0, 4096));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive8192) {
+ tcpSendReturnTest(test_data_.substr(0, 8192));
+}
- socket.close();
+TEST_F(IOFetchTest, TcpSendReceive16384) {
+ tcpSendReturnTest(test_data_.substr(0, 16384));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive32768) {
+ tcpSendReturnTest(test_data_.substr(0, 32768));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive65535) {
+ tcpSendReturnTest(test_data_.substr(0, 65535));
+}
+
+TEST_F(IOFetchTest, TcpSendReceive2ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 2), true);
+}
+
+TEST_F(IOFetchTest, TcpSendReceive15ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 15), true);
+}
- EXPECT_TRUE(run_);
- ASSERT_EQ(expected_buff_->getLength(), buff_->getLength());
- EXPECT_EQ(0, memcmp(expected_buff_->getData(), buff_->getData(),
- expected_buff_->getLength()));
+TEST_F(IOFetchTest, TcpSendReceive8192ShortSend) {
+ tcpSendReturnTest(test_data_.substr(0, 8192), true);
}
diff --git a/src/lib/asiolink/tests/recursive_query_unittest.cc b/src/lib/asiolink/tests/recursive_query_unittest.cc
deleted file mode 100644
index 9760db6..0000000
--- a/src/lib/asiolink/tests/recursive_query_unittest.cc
+++ /dev/null
@@ -1,808 +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 <config.h>
-
-#include <sys/socket.h>
-#include <sys/time.h>
-
-#include <string.h>
-
-#include <boost/lexical_cast.hpp>
-#include <boost/bind.hpp>
-#include <boost/date_time/posix_time/posix_time_types.hpp>
-
-#include <gtest/gtest.h>
-
-#include <exceptions/exceptions.h>
-
-#include <dns/tests/unittest_util.h>
-#include <dns/rcode.h>
-
-#include <dns/buffer.h>
-#include <dns/message.h>
-
-// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
-// In particular, we must not include asio.hpp in this file.
-// The asiolink module is primarily intended to be a wrapper that hide the
-// details of the underlying implementations. We need to test the wrapper
-// level behaviors. In addition, some compilers reject to compile this file
-// if we include asio.hpp unless we specify a special compiler option.
-// If we need to test something at the level of underlying ASIO and need
-// their definition, that test should go to asiolink/internal/tests.
-#include <asiolink/recursive_query.h>
-#include <asiolink/io_socket.h>
-#include <asiolink/io_service.h>
-#include <asiolink/io_message.h>
-#include <asiolink/io_error.h>
-#include <asiolink/dns_lookup.h>
-#include <asiolink/simple_callback.h>
-#include <resolve/resolve.h>
-
-using isc::UnitTestUtil;
-using namespace std;
-using namespace asiolink;
-using namespace isc::dns;
-
-namespace {
-const char* const TEST_SERVER_PORT = "53535";
-const char* const TEST_CLIENT_PORT = "53536";
-const char* const TEST_IPV6_ADDR = "::1";
-const char* const TEST_IPV4_ADDR = "127.0.0.1";
-// This data is intended to be valid as a DNS/TCP-like message: the first
-// two octets encode the length of the rest of the data. This is crucial
-// for the tests below.
-const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
-
-// This function returns an addrinfo structure for use by tests, using
-// different addresses and ports depending on whether we're testing
-// IPv4 or v6, TCP or UDP, and client or server operation.
-struct addrinfo*
-resolveAddress(const int family, const int protocol, const bool client) {
- const char* const addr = (family == AF_INET6) ?
- TEST_IPV6_ADDR : TEST_IPV4_ADDR;
- const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
-
- struct addrinfo hints;
- memset(&hints, 0, sizeof(hints));
- hints.ai_family = family;
- hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
- hints.ai_protocol = protocol;
- hints.ai_flags = AI_NUMERICSERV;
-
- struct addrinfo* res;
- const int error = getaddrinfo(addr, port, &hints, &res);
- if (error != 0) {
- isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
- }
-
- return (res);
-}
-
-// This fixture is a framework for various types of network operations
-// using the ASIO interfaces. Each test case creates an IOService object,
-// opens a local "client" socket for testing, sends data via the local socket
-// to the service that would run in the IOService object.
-// A mock callback function (an ASIOCallBack object) is registered with the
-// IOService object, so the test code should be able to examine the data
-// received on the server side. It then checks the received data matches
-// expected parameters.
-// If initialization parameters of the IOService should be modified, the test
-// case can do it using the setDNSService() method.
-// Note: the set of tests in RecursiveQueryTest use actual network services and may
-// involve undesirable side effects such as blocking.
-class RecursiveQueryTest : public ::testing::Test {
-protected:
- RecursiveQueryTest();
- ~RecursiveQueryTest() {
- if (res_ != NULL) {
- freeaddrinfo(res_);
- }
- if (sock_ != -1) {
- close(sock_);
- }
- delete dns_service_;
- delete callback_;
- delete io_service_;
- }
-
- // Send a test UDP packet to a mock server
- void sendUDP(const int family) {
- res_ = resolveAddress(family, IPPROTO_UDP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
- res_->ai_addr, res_->ai_addrlen);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected sendto result: " << cc);
- }
- io_service_->run();
- }
-
- // Send a test TCP packet to a mock server
- void sendTCP(const int family) {
- res_ = resolveAddress(family, IPPROTO_TCP, false);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to connect to the test server");
- }
- const int cc = send(sock_, test_data, sizeof(test_data), 0);
- if (cc != sizeof(test_data)) {
- isc_throw(IOError, "unexpected send result: " << cc);
- }
- io_service_->run();
- }
-
- // Receive a UDP packet from a mock server; used for testing
- // recursive lookup. The caller must place a RecursiveQuery
- // on the IO Service queue before running this routine.
- void recvUDP(const int family, void* buffer, size_t& size) {
- res_ = resolveAddress(family, IPPROTO_UDP, true);
-
- sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
-
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "bind failed: " << strerror(errno));
- }
-
- // The IO service queue should have a RecursiveQuery object scheduled
- // to run at this point. This call will cause it to begin an
- // async send, then return.
- io_service_->run_one();
-
- // ... and this one will block until the send has completed
- io_service_->run_one();
-
- // Now we attempt to recv() whatever was sent.
- // XXX: there's no guarantee the receiving socket can immediately get
- // the packet. Normally we can perform blocking recv to wait for it,
- // but in theory it's even possible that the packet is lost.
- // In order to prevent the test from hanging in such a worst case
- // we add an ad hoc timeout.
- const struct timeval timeo = { 10, 0 };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
- sizeof(timeo))) {
- if (errno == ENOPROTOOPT) {
- // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
- // with the error of ENOPROTOOPT. Since this is a workaround
- // for rare error cases anyway, we simply switch to the
- // "don't wait" mode. If we still find an error in recv()
- // can happen often we'll consider a more complete solution.
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- const int ret = recv(sock_, buffer, size, recv_options);
- if (ret < 0) {
- isc_throw(IOError, "recvfrom failed: " << strerror(errno));
- }
-
- // Pass the message size back via the size parameter
- size = ret;
- }
-
-
- // Set up an IO Service queue using the specified address
- void setDNSService(const char& address) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
- }
-
- // Set up an IO Service queue using the "any" address, on IPv4 if
- // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
- void setDNSService(const bool use_ipv4, const bool use_ipv6) {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
- NULL, NULL);
- }
-
- // Set up empty DNS Service
- // Set up an IO Service queue without any addresses
- void setDNSService() {
- delete dns_service_;
- dns_service_ = NULL;
- delete io_service_;
- io_service_ = new IOService();
- callback_ = new ASIOCallBack(this);
- dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
- }
-
- // Run a simple server test, on either IPv4 or IPv6, and over either
- // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
- // start the IO Service queue. The UDPServer or TCPServer that was
- // created by setIOService() will receive the test packet and issue a
- // callback, which enables us to check that the data it received
- // matches what we sent.
- void doTest(const int family, const int protocol) {
- if (protocol == IPPROTO_UDP) {
- sendUDP(family);
- } else {
- sendTCP(family);
- }
-
- // There doesn't seem to be an effective test for the validity of
- // 'native'.
- // One thing we are sure is it must be different from our local socket.
- EXPECT_NE(sock_, callback_native_);
- EXPECT_EQ(protocol, callback_protocol_);
- EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
- callback_address_);
-
- const uint8_t* expected_data =
- protocol == IPPROTO_UDP ? test_data : test_data + 2;
- const size_t expected_datasize =
- protocol == IPPROTO_UDP ? sizeof(test_data) :
- sizeof(test_data) - 2;
- EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
- callback_data_.size(),
- expected_data, expected_datasize);
- }
-
-protected:
- // This is a nonfunctional mockup of a DNSServer object. Its purpose
- // is to resume after a recursive query or other asynchronous call
- // has completed.
- class MockServer : public DNSServer {
- public:
- explicit MockServer(IOService& io_service,
- SimpleCallback* checkin = NULL,
- DNSLookup* lookup = NULL,
- DNSAnswer* answer = NULL) :
- io_(io_service),
- done_(false),
- message_(new Message(Message::PARSE)),
- answer_message_(new Message(Message::RENDER)),
- respbuf_(new OutputBuffer(0)),
- checkin_(checkin), lookup_(lookup), answer_(answer)
- {}
-
- void operator()(asio::error_code ec = asio::error_code(),
- size_t length = 0)
- {}
-
- void resume(const bool) {
- // should never be called in our tests
- }
-
- DNSServer* clone() {
- MockServer* s = new MockServer(*this);
- return (s);
- }
-
- inline void asyncLookup() {
- if (lookup_) {
- (*lookup_)(*io_message_, message_, answer_message_,
- respbuf_, this);
- }
- }
-
- protected:
- IOService& io_;
- bool done_;
-
- private:
- // Currently unused; these will be used for testing
- // asynchronous lookup calls via the asyncLookup() method
- boost::shared_ptr<asiolink::IOMessage> io_message_;
- isc::dns::MessagePtr message_;
- isc::dns::MessagePtr answer_message_;
- isc::dns::OutputBufferPtr respbuf_;
-
- // Callback functions provided by the caller
- const SimpleCallback* checkin_;
- const DNSLookup* lookup_;
- const DNSAnswer* answer_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- class MockServerStop : public MockServer {
- public:
- explicit MockServerStop(IOService& io_service, bool* done) :
- MockServer(io_service),
- done_(done)
- {}
-
- void resume(const bool done) {
- *done_ = done;
- io_.stop();
- }
-
- DNSServer* clone() {
- return (new MockServerStop(*this));
- }
- private:
- bool* done_;
- };
-
- // This version of mock server just stops the io_service when it is resumed
- // the second time. (Used in the clientTimeout test, where resume
- // is called initially with the error answer, and later when the
- // lookup times out, it is called without an answer to send back)
- class MockServerStop2 : public MockServer {
- public:
- explicit MockServerStop2(IOService& io_service,
- bool* done1, bool* done2) :
- MockServer(io_service),
- done1_(done1),
- done2_(done2),
- stopped_once_(false)
- {}
-
- void resume(const bool done) {
- if (stopped_once_) {
- *done2_ = done;
- io_.stop();
- } else {
- *done1_ = done;
- stopped_once_ = true;
- }
- }
-
- DNSServer* clone() {
- return (new MockServerStop2(*this));
- }
- private:
- bool* done1_;
- bool* done2_;
- bool stopped_once_;
- };
-
-private:
- class ASIOCallBack : public SimpleCallback {
- public:
- ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
- void operator()(const IOMessage& io_message) const {
- test_obj_->callBack(io_message);
- }
- private:
- RecursiveQueryTest* test_obj_;
- };
- void callBack(const IOMessage& io_message) {
- callback_protocol_ = io_message.getSocket().getProtocol();
- callback_native_ = io_message.getSocket().getNative();
- callback_address_ =
- io_message.getRemoteEndpoint().getAddress().toText();
- callback_data_.assign(
- static_cast<const uint8_t*>(io_message.getData()),
- static_cast<const uint8_t*>(io_message.getData()) +
- io_message.getDataSize());
- io_service_->stop();
- }
-protected:
- // We use a pointer for io_service_, because for some tests we
- // need to recreate a new one within one onstance of this class
- IOService* io_service_;
- DNSService* dns_service_;
- ASIOCallBack* callback_;
- int callback_protocol_;
- int callback_native_;
- string callback_address_;
- vector<uint8_t> callback_data_;
- int sock_;
- struct addrinfo* res_;
-};
-
-RecursiveQueryTest::RecursiveQueryTest() :
- dns_service_(NULL), callback_(NULL), callback_protocol_(0),
- callback_native_(-1), sock_(-1), res_(NULL)
-{
- io_service_ = new IOService();
- setDNSService(true, true);
-}
-
-TEST_F(RecursiveQueryTest, v6UDPSend) {
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPSend) {
- doTest(AF_INET6, IPPROTO_TCP);
-}
-
-TEST_F(RecursiveQueryTest, v4UDPSend) {
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPSend) {
- doTest(AF_INET, IPPROTO_TCP);
-}
-
-TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
- // Explicitly set a specific address to be bound to the socket.
- // The subsequent test does not directly ensures the underlying socket
- // is bound to the expected address, but the success of the tests should
- // reasonably suggest it works as intended.
- // Specifying an address also implicitly means the service runs in a
- // single address-family mode. In tests using TCP we can confirm that
- // by trying to make a connection and seeing a failure. In UDP, it'd be
- // more complicated because we need to use a connected socket and catch
- // an error on a subsequent read operation. We could do it, but for
- // simplicity we only tests the easier cases for now.
-
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
- setDNSService(*TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_UDP);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
- setDNSService(*TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v6AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
- doTest(AF_INET6, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4AddServer) {
- setDNSService();
- dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
- doTest(AF_INET, IPPROTO_TCP);
-
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, clearServers) {
- setDNSService();
- dns_service_->clearServers();
-
- EXPECT_THROW(sendTCP(AF_INET), IOError);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v6TCPOnly) {
- // Open only IPv6 TCP socket. A subsequent attempt of establishing an
- // IPv4/TCP connection should fail. See above for why we only test this
- // for TCP.
- setDNSService(false, true);
- EXPECT_THROW(sendTCP(AF_INET), IOError);
-}
-
-TEST_F(RecursiveQueryTest, v4TCPOnly) {
- setDNSService(true, false);
- EXPECT_THROW(sendTCP(AF_INET6), IOError);
-}
-
-vector<pair<string, uint16_t> >
-singleAddress(const string &address, uint16_t port) {
- vector<pair<string, uint16_t> > result;
- result.push_back(pair<string, uint16_t>(address, port));
- return (result);
-}
-
-TEST_F(RecursiveQueryTest, recursiveSetupV4) {
- setDNSService(true, false);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port)));
-}
-
-TEST_F(RecursiveQueryTest, recursiveSetupV6) {
- setDNSService(false, true);
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
- singleAddress(TEST_IPV6_ADDR, port),
- singleAddress(TEST_IPV6_ADDR,port)));
-}
-
-// XXX:
-// This is very inadequate unit testing. It should be generalized into
-// a routine that can do this with variable address family, address, and
-// port, and with the various callbacks defined in such a way as to ensure
-// full code coverage including error cases.
-TEST_F(RecursiveQueryTest, forwarderSend) {
- setDNSService(true, false);
-
- // Note: We use the test prot plus one to ensure we aren't binding
- // to the same port as the actual server
- uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
-
- MockServer server(*io_service_);
- RecursiveQuery rq(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port));
-
- Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
- Message query_message(Message::RENDER);
- isc::resolve::initResponseMessage(q, query_message);
-
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
-
- char data[4096];
- size_t size = sizeof(data);
- ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
-
- Message m(Message::PARSE);
- InputBuffer ibuf(data, size);
-
- // Make sure we can parse the message that was sent
- EXPECT_NO_THROW(m.parseHeader(ibuf));
- EXPECT_NO_THROW(m.fromWire(ibuf));
-
- // Check that the question sent matches the one we wanted
- QuestionPtr q2 = *m.beginQuestion();
- EXPECT_EQ(q.getName(), q2->getName());
- EXPECT_EQ(q.getType(), q2->getType());
- EXPECT_EQ(q.getClass(), q2->getClass());
-}
-
-int
-createTestSocket()
-{
- struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
- int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
- if (sock_ < 0) {
- isc_throw(IOError, "failed to open test socket");
- }
- if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
- isc_throw(IOError, "failed to bind test socket");
- }
- return sock_;
-}
-
-int
-setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
- const struct timeval timeo = { tv_sec, tv_usec };
- int recv_options = 0;
- if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
- if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
- recv_options = MSG_DONTWAIT;
- } else {
- isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
- }
- }
- return recv_options;
-}
-
-// try to read from the socket max time
-// *num is incremented for every succesfull read
-// returns true if it can read max times, false otherwise
-bool tryRead(int sock_, int recv_options, size_t max, int* num) {
- size_t i = 0;
- do {
- char inbuff[512];
- if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
- return false;
- } else {
- ++i;
- ++*num;
- }
- } while (i < max);
- return true;
-}
-
-
-// Test it tries the correct amount of times before giving up
-TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 10, 4000, 3000, 2);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
-
- Message query_message(Message::RENDER);
- isc::resolve::initResponseMessage(question, query_message);
- query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
- // Run the test
- io_service_->run();
-
- // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
- // block (see also recvUDP()).
- int recv_options = setSocketTimeout(sock_, 10, 0);
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 3, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set client timeout to lower than querytimeout, we should
-// get a failure answer, but still see retries
-// (no actual answer is given here yet)
-TEST_F(RecursiveQueryTest, forwardClientTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done1(true);
- bool done2(true);
- MockServerStop2 server(*io_service_, &done1, &done2);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set it up to retry twice before client timeout fires
- // Since the lookup timer has not fired, it should retry
- // four times
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 200, 480, 4000, 4);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
-
- Message query_message(Message::RENDER);
- isc::resolve::initResponseMessage(question, query_message);
- query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- // we know it'll fail, so make it a shorter timeout
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail, but we should have kept on trying
- EXPECT_TRUE(done1);
- EXPECT_FALSE(done2);
- EXPECT_EQ(5, num);
- EXPECT_TRUE(read_success);
-}
-
-// If we set lookup timeout to lower than querytimeout*retries, we should
-// fail before the full amount of retries
-TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
- // Prepare the service (we do not use the common setup, we do not answer
- setDNSService();
-
- // Prepare the socket
- sock_ = createTestSocket();
-
- // Prepare the server
- bool done(true);
- MockServerStop server(*io_service_, &done);
-
- MessagePtr answer(new Message(Message::RENDER));
-
- // Do the answer
- const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
- // Set up the test so that it will retry 5 times, but the lookup
- // timeout will fire after only 3 normal timeouts
- RecursiveQuery query(*dns_service_,
- singleAddress(TEST_IPV4_ADDR, port),
- singleAddress(TEST_IPV4_ADDR, port),
- 200, 4000, 480, 5);
- Question question(Name("example.net"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
-
- Message query_message(Message::RENDER);
- isc::resolve::initResponseMessage(question, query_message);
- query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
-
- // Run the test
- io_service_->run();
-
- int recv_options = setSocketTimeout(sock_, 1, 0);
-
- // Try to read 5 times, should stop after 3 reads
- int num = 0;
- bool read_success = tryRead(sock_, recv_options, 5, &num);
-
- // The query should fail
- EXPECT_FALSE(done);
- EXPECT_EQ(3, num);
- EXPECT_FALSE(read_success);
-}
-
-// as mentioned above, we need a more better framework for this,
-// in addition to that, this sends out queries into the world
-// (which we should catch somehow and fake replies for)
-// for the skeleton code, it shouldn't be too much of a problem
-// Ok so even we don't all have access to the DNS world right now,
-// so disabling these tests too.
-TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
- ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
- RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
- EXPECT_EQ(q.getName(), a->getName());
- EXPECT_EQ(q.getType(), a->getType());
- EXPECT_EQ(q.getClass(), a->getClass());
- EXPECT_EQ(1, a->getRdataCount());
-}
-
-// see comments at previous test
-TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
- setDNSService(true, false);
- bool done;
-
- MockServerStop server(*io_service_, &done);
- vector<pair<string, uint16_t> > empty_vector;
- RecursiveQuery rq(*dns_service_, empty_vector, empty_vector, 10000, 0);
-
- Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
- OutputBufferPtr buffer(new OutputBuffer(0));
- MessagePtr answer(new Message(Message::RENDER));
- rq.resolve(q, answer, buffer, &server);
- io_service_->run();
-
- // Check that the answer we got matches the one we wanted
- EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
- EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
-}
-
-}
diff --git a/src/lib/asiolink/tests/run_unittests.cc b/src/lib/asiolink/tests/run_unittests.cc
index b481784..c285f9e 100644
--- a/src/lib/asiolink/tests/run_unittests.cc
+++ b/src/lib/asiolink/tests/run_unittests.cc
@@ -14,13 +14,15 @@
#include <gtest/gtest.h>
+#include <log/root_logger_name.h>
#include <dns/tests/unittest_util.h>
int
main(int argc, char* argv[])
{
- ::testing::InitGoogleTest(&argc, argv);
- isc::UnitTestUtil::addDataPath(TEST_DATA_DIR);
+ ::testing::InitGoogleTest(&argc, argv); // Initialize Google test
+ isc::log::setRootLoggerName("unittest"); // Set a root logger name
+ isc::UnitTestUtil::addDataPath(TEST_DATA_DIR); // Add location of test data
return (RUN_ALL_TESTS());
}
diff --git a/src/lib/asiolink/tests/tcp_endpoint_unittest.cc b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
new file mode 100644
index 0000000..3787e1c
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_endpoint_unittest.cc
@@ -0,0 +1,55 @@
+// 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 <config.h>
+
+#include <string>
+
+#include <gtest/gtest.h>
+
+#include <asio.hpp>
+#include <asiolink/io_address.h>
+#include <asiolink/tcp_endpoint.h>
+
+using namespace asiolink;
+using namespace std;
+
+// This test checks that the endpoint can manage its own internal
+// asio::ip::tcp::endpoint object.
+
+TEST(TCPEndpointTest, v4Address) {
+ const string test_address("192.0.2.1");
+ const unsigned short test_port = 5301;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET, endpoint.getFamily());
+}
+
+TEST(TCPEndpointTest, v6Address) {
+ const string test_address("2001:db8::1235");
+ const unsigned short test_port = 5302;
+
+ IOAddress address(test_address);
+ TCPEndpoint endpoint(address, test_port);
+
+ EXPECT_TRUE(address == endpoint.getAddress());
+ EXPECT_EQ(test_port, endpoint.getPort());
+ EXPECT_EQ(IPPROTO_TCP, endpoint.getProtocol());
+ EXPECT_EQ(AF_INET6, endpoint.getFamily());
+}
diff --git a/src/lib/asiolink/tests/tcp_socket_unittest.cc b/src/lib/asiolink/tests/tcp_socket_unittest.cc
new file mode 100644
index 0000000..f0a45ee
--- /dev/null
+++ b/src/lib/asiolink/tests/tcp_socket_unittest.cc
@@ -0,0 +1,515 @@
+// 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.
+
+/// \brief Test of TCPSocket
+///
+/// Tests the fuctionality of a TCPSocket by working through an open-send-
+/// receive-close sequence and checking that the asynchronous notifications
+/// work.
+
+#include <string>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstddef>
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <dns/buffer.h>
+
+#include <asio.hpp>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/io_service.h>
+#include <asiolink/tcp_endpoint.h>
+#include <asiolink/tcp_socket.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace asiolink;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+const char SERVER_ADDRESS[] = "127.0.0.1";
+const unsigned short SERVER_PORT = 5303;
+
+// TODO: Shouldn't we send something that is real message?
+const char OUTBOUND_DATA[] = "Data sent from client to server";
+const char INBOUND_DATA[] = "Returned data from server to client";
+}
+
+/// An instance of this object is passed to the asynchronous I/O functions
+/// and the operator() method is called when when an asynchronous I/O completes.
+/// The arguments to the completion callback are stored for later retrieval.
+class TCPCallback {
+public:
+ /// \brief Operations the server is doing
+ enum Operation {
+ ACCEPT = 0, ///< accept() was issued
+ OPEN = 1, /// Client connected to server
+ READ = 2, ///< Asynchronous read completed
+ WRITE = 3, ///< Asynchronous write completed
+ NONE = 4 ///< "Not set" state
+ };
+
+ /// \brief Minimim size of buffers
+ enum {
+ MIN_SIZE = (64 * 1024 + 2) ///< 64kB + two bytes for a count
+ };
+
+ struct PrivateData {
+ PrivateData() :
+ error_code_(), length_(0), cumulative_(0), expected_(0), offset_(0),
+ name_(""), queued_(NONE), called_(NONE)
+ {}
+
+ asio::error_code error_code_; ///< Completion error code
+ size_t length_; ///< Bytes transferred in this I/O
+ size_t cumulative_; ///< Cumulative bytes transferred
+ size_t expected_; ///< Expected amount of data
+ size_t offset_; ///< Where to put data in buffer
+ std::string name_; ///< Which of the objects this is
+ Operation queued_; ///< Queued operation
+ Operation called_; ///< Which callback called
+ uint8_t data_[MIN_SIZE]; ///< Receive buffer
+
+ };
+
+ /// \brief Constructor
+ ///
+ /// Constructs the object. It also creates the data member pointed to by
+ /// a shared pointer. When used as a callback object, this is copied as it
+ /// is passed into the asynchronous function. This means that there are two
+ /// objects and inspecting the one we passed in does not tell us anything.
+ ///
+ /// Therefore we use a boost::shared_ptr. When the object is copied, the
+ /// shared pointer is copied, which leaves both objects pointing to the same
+ /// data.
+ ///
+ /// \param which Which of the two callback objects this is
+ TCPCallback(std::string which) : ptr_(new PrivateData())
+ {
+ ptr_->name_ = which;
+ }
+
+ /// \brief Destructor
+ ///
+ /// No code needed, destroying the shared pointer destroys the private data.
+ virtual ~TCPCallback()
+ {}
+
+ /// \brief Client Callback Function
+ ///
+ /// Called when an asynchronous operation is completed by the client, this
+ /// stores the origin of the operation in the client_called_ data member.
+ ///
+ /// \param ec I/O completion error code passed to callback function.
+ /// \param length Number of bytes transferred
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {
+ setCode(ec.value());
+ ptr_->called_ = ptr_->queued_;
+ ptr_->length_ = length;
+ }
+
+ /// \brief Get I/O completion error code
+ int getCode() {
+ return (ptr_->error_code_.value());
+ }
+
+ /// \brief Set I/O completion code
+ ///
+ /// \param code New value of completion code
+ void setCode(int code) {
+ ptr_->error_code_ = asio::error_code(code, asio::error_code().category());
+ }
+
+ /// \brief Get number of bytes transferred in I/O
+ size_t& length() {
+ return (ptr_->length_);
+ }
+
+ /// \brief Get cumulative number of bytes transferred in I/O
+ size_t& cumulative() {
+ return (ptr_->cumulative_);
+ }
+
+ /// \brief Get expected amount of data
+ size_t& expected() {
+ return (ptr_->expected_);
+ }
+
+ /// \brief Get offset intodData
+ size_t& offset() {
+ return (ptr_->offset_);
+ }
+
+ /// \brief Get data member
+ uint8_t* data() {
+ return (ptr_->data_);
+ }
+
+ /// \brief Get flag to say what was queued
+ Operation& queued() {
+ return (ptr_->queued_);
+ }
+
+ /// \brief Get flag to say when callback was called
+ Operation& called() {
+ return (ptr_->called_);
+ }
+
+ /// \brief Return instance of callback name
+ std::string& name() {
+ return (ptr_->name_);
+ }
+
+private:
+ boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
+};
+
+
+// Read Server Data
+//
+// Called in the part of the test that has the client send a message to the
+// server, this loops until all the data has been read (synchronously) by the
+// server.
+//
+// "All the data read" means that the server has received a message that is
+// preceded by a two-byte count field and that the total amount of data received
+// from the remote end is equal to the value in the count field plus two bytes
+// for the count field itself.
+//
+// \param socket Socket on which the server is reading data
+// \param server_cb Structure in which server data is held.
+void
+serverRead(tcp::socket& socket, TCPCallback& server_cb) {
+
+ // As we may need to read multiple times, keep a count of the cumulative
+ // amount of data read and do successive reads into the appropriate part
+ // of the buffer.
+ //
+ // Note that there are no checks for buffer overflow - this is a test
+ // program and we have sized the buffer to be large enough for the test.
+ server_cb.cumulative() = 0;
+
+ bool complete = false;
+ while (!complete) {
+
+ // Read block of data and update cumulative amount of data received.
+ server_cb.length() = socket.receive(
+ asio::buffer(server_cb.data() + server_cb.cumulative(),
+ TCPCallback::MIN_SIZE - server_cb.cumulative()));
+ server_cb.cumulative() += server_cb.length();
+
+ // If we have read at least two bytes, we can work out how much we
+ // should be reading.
+ if (server_cb.cumulative() >= 2) {
+ server_cb.expected() = readUint16(server_cb.data());
+ if ((server_cb.expected() + 2) == server_cb.cumulative()) {
+
+ // Amount of data read from socket equals the size of the
+ // message (as indicated in the first two bytes of the message)
+ // plus the size of the count field. Therefore we have received
+ // all the data.
+ complete = true;
+ }
+ }
+ }
+}
+
+// Receive complete method should return true only if the count in the first
+// two bytes is equal to the size of the rest if the buffer.
+
+TEST(TCPSocket, processReceivedData) {
+ const uint16_t PACKET_SIZE = 16382; // Amount of "real" data in the buffer
+
+ IOService service; // Used to instantiate socket
+ TCPSocket<TCPCallback> test(service); // Socket under test
+ uint8_t inbuff[PACKET_SIZE + 2]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ size_t expected; // Expected amount of data
+ size_t offset; // Where to put next data
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (size_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i % 256;
+ }
+
+ // Check that the method will handle various receive sizes.
+ writeUint16(PACKET_SIZE, inbuff);
+
+ cumulative = 0;
+ offset = 0;
+ expected = 0;
+ outbuff->clear();
+ bool complete = test.processReceivedData(inbuff, 1, cumulative, offset,
+ expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(1, cumulative);
+ EXPECT_EQ(1, offset);
+ EXPECT_EQ(0, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Now pretend that we've received one more byte.
+ complete = test.processReceivedData(inbuff, 1, cumulative, offset, expected,
+ outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(0, outbuff->getLength());
+
+ // Add another two bytes. However, this time note that we have to offset
+ // in the input buffer because it is expected that the next chunk of data
+ // from the connection will be read into the start of the buffer.
+ complete = test.processReceivedData(inbuff + cumulative, 2, cumulative,
+ offset, expected, outbuff);
+ EXPECT_FALSE(complete);
+ EXPECT_EQ(4, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(2, outbuff->getLength());
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+
+ // And add the remaining data. Remember that "inbuff" is "PACKET_SIZE + 2"
+ // long.
+ complete = test.processReceivedData(inbuff + cumulative,
+ PACKET_SIZE + 2 - cumulative,
+ cumulative, offset, expected, outbuff);
+ EXPECT_TRUE(complete);
+ EXPECT_EQ(PACKET_SIZE + 2, cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(PACKET_SIZE, expected);
+ EXPECT_EQ(PACKET_SIZE, outbuff->getLength());
+ dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff + 2, inbuff + cumulative, dataptr));
+}
+
+// TODO: Need to add a test to check the cancel() method
+
+// Tests the operation of a TCPSocket by opening it, sending an asynchronous
+// message to a server, receiving an asynchronous message from the server and
+// closing.
+TEST(TCPSocket, SequenceTest) {
+
+ // Common objects.
+ IOService service; // Service object for async control
+
+ // The client - the TCPSocket being tested
+ TCPSocket<TCPCallback> client(service);// Socket under test
+ TCPCallback client_cb("Client"); // Async I/O callback function
+ TCPEndpoint client_remote_endpoint; // Where client receives message from
+ OutputBufferPtr client_buffer(new OutputBuffer(128));
+ // Received data is put here
+
+ // The server - with which the client communicates.
+ IOAddress server_address(SERVER_ADDRESS);
+ // Address of target server
+ TCPCallback server_cb("Server"); // Server callback
+ TCPEndpoint server_endpoint(server_address, SERVER_PORT);
+ // Endpoint describing server
+ TCPEndpoint server_remote_endpoint; // Address where server received message from
+ tcp::socket server_socket(service.get_io_service());
+ // Socket used for server
+
+ // Step 1. Create the connection between the client and the server. Set
+ // up the server to accept incoming connections and have the client open
+ // a channel to it.
+
+ // Set up server - open socket and queue an accept.
+ server_cb.queued() = TCPCallback::ACCEPT;
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.setCode(42); // Some error
+ tcp::acceptor acceptor(service.get_io_service(),
+ tcp::endpoint(tcp::v4(), SERVER_PORT));
+ acceptor.set_option(tcp::acceptor::reuse_address(true));
+ acceptor.async_accept(server_socket, server_cb);
+
+ // Set up client - connect to the server.
+ client_cb.queued() = TCPCallback::OPEN;
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.setCode(43); // Some error
+ EXPECT_FALSE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
+
+ // Run the open and the accept callback and check that they ran.
+ service.run_one();
+ service.run_one();
+
+ EXPECT_EQ(TCPCallback::ACCEPT, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+
+ EXPECT_EQ(TCPCallback::OPEN, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Step 2. Get the client to write to the server asynchronously. The
+ // server will loop reading the data synchronously.
+
+ // Write asynchronously to the server.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::WRITE;
+ client_cb.setCode(143); // Arbitrary number
+ client_cb.length() = 0;
+ client.asyncSend(OUTBOUND_DATA, sizeof(OUTBOUND_DATA), &server_endpoint, client_cb);
+
+ // Wait for the client callback to complete. (Must do this first on
+ // Solaris: if we do the synchronous read first, the test hangs.)
+ service.run_one();
+
+ // Synchronously read the data from the server.;
+ serverRead(server_socket, server_cb);
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::WRITE, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, client_cb.length());
+
+ // ... and check what the server received.
+ EXPECT_EQ(sizeof(OUTBOUND_DATA) + 2, server_cb.cumulative());
+ EXPECT_TRUE(equal(OUTBOUND_DATA,
+ (OUTBOUND_DATA + (sizeof(OUTBOUND_DATA) - 1)),
+ (server_cb.data() + 2)));
+
+ // Step 3. Get the server to write all the data asynchronously and have the
+ // client loop (asynchronously) reading the data. Note that we copy the
+ // data into the server's internal buffer in order to precede it with a two-
+ // byte count field.
+
+ // Have the server write asynchronously to the client.
+ server_cb.called() = TCPCallback::NONE;
+ server_cb.queued() = TCPCallback::WRITE;
+ server_cb.length() = 0;
+ server_cb.cumulative() = 0;
+
+ writeUint16(sizeof(INBOUND_DATA), server_cb.data());
+ copy(INBOUND_DATA, (INBOUND_DATA + sizeof(INBOUND_DATA) - 1),
+ (server_cb.data() + 2));
+ server_socket.async_send(asio::buffer(server_cb.data(),
+ (sizeof(INBOUND_DATA) + 2)),
+ server_cb);
+
+ // Have the client read asynchronously.
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client_cb.cumulative() = 0;
+ client_cb.expected() = 0;
+ client_cb.offset() = 0;
+
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+
+ // Run the callbacks. Several options are possible depending on how ASIO
+ // is implemented and whether the message gets fragmented:
+ //
+ // 1) The send handler may complete immediately, regardess of whether the
+ // data has been read by the client. (This is the most likely.)
+ // 2) The send handler may only run after all the data has been read by
+ // the client. (This could happen if the client's TCP buffers were too
+ // small so the data was not transferred to the "remote" system until the
+ // remote buffer has been emptied one or more times.)
+ // 3) The client handler may be run a number of times to handle the message
+ // fragments and the server handler may run between calls of the client
+ // handler.
+ //
+ // So loop, running one handler at a time until we are certain that all the
+ // handlers have run.
+
+ bool server_complete = false;
+ bool client_complete = false;
+ while (!server_complete || !client_complete) {
+ service.run_one();
+
+ // Has the server run?
+ if (!server_complete) {
+ if (server_cb.called() == server_cb.queued()) {
+
+ // Yes. Check that the send completed successfully and that
+ // all the data that was expected to have been sent was in fact
+ // sent.
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ((sizeof(INBOUND_DATA) + 2), server_cb.length());
+ server_complete = true;
+ continue;
+ }
+ }
+
+ if (!client_complete) {
+
+ // Client callback must have run. Check that it ran OK.
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+
+ // Check if we need to queue another read, copying the data into
+ // the output buffer as we do so.
+ client_complete = client.processReceivedData(client_cb.data(),
+ client_cb.length(),
+ client_cb.cumulative(),
+ client_cb.offset(),
+ client_cb.expected(),
+ client_buffer);
+
+ // If the data is not complete, queue another read.
+ if (! client_complete) {
+ client_cb.called() = TCPCallback::NONE;
+ client_cb.queued() = TCPCallback::READ;
+ client_cb.length() = 0;
+ client.asyncReceive(client_cb.data(), TCPCallback::MIN_SIZE ,
+ client_cb.offset(), &client_remote_endpoint,
+ client_cb);
+ }
+ }
+ }
+
+ // Both the send and the receive have completed. Check that the received
+ // is what was sent.
+
+ // Check the client state
+ EXPECT_EQ(TCPCallback::READ, client_cb.called());
+ EXPECT_EQ(0, client_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, client_cb.cumulative());
+ EXPECT_EQ(sizeof(INBOUND_DATA), client_buffer->getLength());
+
+ // ... and check what the server sent.
+ EXPECT_EQ(TCPCallback::WRITE, server_cb.called());
+ EXPECT_EQ(0, server_cb.getCode());
+ EXPECT_EQ(sizeof(INBOUND_DATA) + 2, server_cb.length());
+
+ // ... and that what was sent is what was received.
+ const uint8_t* received = static_cast<const uint8_t*>(client_buffer->getData());
+ EXPECT_TRUE(equal(INBOUND_DATA, (INBOUND_DATA + (sizeof(INBOUND_DATA) - 1)),
+ received));
+
+ // Close client and server.
+ EXPECT_NO_THROW(client.close());
+ EXPECT_NO_THROW(server_socket.close());
+}
diff --git a/src/lib/asiolink/tests/udp_socket_unittest.cc b/src/lib/asiolink/tests/udp_socket_unittest.cc
index bb79b88..8563d22 100644
--- a/src/lib/asiolink/tests/udp_socket_unittest.cc
+++ b/src/lib/asiolink/tests/udp_socket_unittest.cc
@@ -12,21 +12,6 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
-// 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.
-
-
/// \brief Test of UDPSocket
///
/// Tests the fuctionality of a UDPSocket by working through an open-send-
@@ -50,14 +35,18 @@
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
+#include <dns/buffer.h>
+
#include <asio.hpp>
+#include <asiolink/asiolink_utilities.h>
#include <asiolink/io_service.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_socket.h>
using namespace asio;
using namespace asiolink;
+using namespace isc::dns;
using namespace std;
namespace {
@@ -177,6 +166,49 @@ private:
boost::shared_ptr<PrivateData> ptr_; ///< Pointer to private data
};
+// Receive complete method should return true regardless of what is in the first
+// two bytes of a buffer.
+
+TEST(UDPSocket, processReceivedData) {
+ IOService service; // Used to instantiate socket
+ UDPSocket<UDPCallback> test(service); // Socket under test
+ uint8_t inbuff[32]; // Buffer to check
+ OutputBufferPtr outbuff(new OutputBuffer(16));
+ // Where data is put
+ size_t expected; // Expected amount of data
+ size_t offset; // Where to put next data
+ size_t cumulative; // Cumulative data received
+
+ // Set some dummy values in the buffer to check
+ for (uint8_t i = 0; i < sizeof(inbuff); ++i) {
+ inbuff[i] = i;
+ }
+
+ // Expect that the value is true whatever number is written in the first
+ // two bytes of the buffer.
+ uint16_t count = 0;
+ for (uint32_t i = 0; i < (2 << 16); ++i, ++count) {
+ writeUint16(count, inbuff);
+
+ // Set some random values
+ cumulative = 5;
+ offset = 10;
+ expected = 15;
+ outbuff->clear();
+
+ bool completed = test.processReceivedData(inbuff, sizeof(inbuff),
+ cumulative, offset, expected,
+ outbuff);
+ EXPECT_TRUE(completed);
+ EXPECT_EQ(sizeof(inbuff), cumulative);
+ EXPECT_EQ(0, offset);
+ EXPECT_EQ(sizeof(inbuff), expected);
+
+ const uint8_t* dataptr = static_cast<const uint8_t*>(outbuff->getData());
+ EXPECT_TRUE(equal(inbuff, inbuff + sizeof(inbuff) - 1, dataptr));
+ }
+}
+
// TODO: Need to add a test to check the cancel() method
// Tests the operation of a UDPSocket by opening it, sending an asynchronous
@@ -199,6 +231,10 @@ TEST(UDPSocket, SequenceTest) {
UDPCallback client_cb("Client"); // Async I/O callback function
UDPEndpoint client_remote_endpoint; // Where client receives message from
size_t client_cumulative = 0; // Cumulative data received
+ size_t client_offset = 0; // Offset into buffer where data is put
+ size_t client_expected = 0; // Expected amount of data
+ OutputBufferPtr client_buffer(new OutputBuffer(16));
+ // Where data is put
// The server - with which the client communicates. For convenience, we
// use the same io_service, and use the endpoint object created for
@@ -208,11 +244,12 @@ TEST(UDPSocket, SequenceTest) {
server.set_option(socket_base::reuse_address(true));
// Assertion to ensure that the server buffer is large enough
- char data[UDPSocket<UDPCallback>::MAX_SIZE];
+ char data[UDPSocket<UDPCallback>::MIN_SIZE];
ASSERT_GT(sizeof(data), sizeof(OUTBOUND_DATA));
// Open the client socket - the operation should be synchronous
- EXPECT_FALSE(client.open(&server_endpoint, client_cb));
+ EXPECT_TRUE(client.isOpenSynchronous());
+ client.open(&server_endpoint, client_cb);
// Issue read on the server. Completion callback should not have run.
server_cb.setCalled(false);
@@ -257,7 +294,7 @@ TEST(UDPSocket, SequenceTest) {
server.async_send_to(buffer(INBOUND_DATA, sizeof(INBOUND_DATA)),
server_remote_endpoint.getASIOEndpoint(), server_cb);
- // Expect the two callbacks to run
+ // Expect two callbacks to run.
service.run_one();
service.run_one();
@@ -276,10 +313,19 @@ TEST(UDPSocket, SequenceTest) {
EXPECT_TRUE(server_address == client_remote_endpoint.getAddress());
EXPECT_EQ(SERVER_PORT, client_remote_endpoint.getPort());
- // Finally, check that the receive received a complete buffer's worth of data.
- EXPECT_TRUE(client.receiveComplete(&data[0], client_cb.getLength(),
- client_cumulative));
+ // Check that the receive received a complete buffer's worth of data.
+ EXPECT_TRUE(client.processReceivedData(&data[0], client_cb.getLength(),
+ client_cumulative, client_offset,
+ client_expected, client_buffer));
+
EXPECT_EQ(client_cb.getLength(), client_cumulative);
+ EXPECT_EQ(0, client_offset);
+ EXPECT_EQ(client_cb.getLength(), client_expected);
+ EXPECT_EQ(client_cb.getLength(), client_buffer->getLength());
+
+ // ...and check that the data was copied to the output client buffer.
+ const char* client_char_data = static_cast<const char*>(client_buffer->getData());
+ EXPECT_TRUE(equal(&data[0], &data[client_cb.getLength() - 1], client_char_data));
// Close client and server.
EXPECT_NO_THROW(client.close());
diff --git a/src/lib/asiolink/udp_endpoint.h b/src/lib/asiolink/udp_endpoint.h
index 0958af6..99dc27f 100644
--- a/src/lib/asiolink/udp_endpoint.h
+++ b/src/lib/asiolink/udp_endpoint.h
@@ -64,6 +64,17 @@ public:
asio_endpoint_placeholder_(NULL), asio_endpoint_(asio_endpoint)
{}
+ /// \brief Constructor from an ASIO UDP endpoint.
+ ///
+ /// This constructor is designed to be an efficient wrapper for the
+ /// corresponding ASIO class, \c udp::endpoint.
+ ///
+ /// \param asio_endpoint The ASIO representation of the TCP endpoint.
+ UDPEndpoint(const asio::ip::udp::endpoint& asio_endpoint) :
+ asio_endpoint_placeholder_(new asio::ip::udp::endpoint(asio_endpoint)),
+ asio_endpoint_(*asio_endpoint_placeholder_)
+ {}
+
/// \brief The destructor.
virtual ~UDPEndpoint() { delete asio_endpoint_placeholder_; }
//@}
diff --git a/src/lib/asiolink/udp_server.cc b/src/lib/asiolink/udp_server.cc
index 063926e..5b48f28 100644
--- a/src/lib/asiolink/udp_server.cc
+++ b/src/lib/asiolink/udp_server.cc
@@ -24,6 +24,7 @@
#include <log/dummylog.h>
#include <asio.hpp>
+#include <asio/error.hpp>
#include <asiolink/dummy_io_cb.h>
#include <asiolink/udp_endpoint.h>
#include <asiolink/udp_server.h>
@@ -56,7 +57,7 @@ struct UDPServer::Data {
*/
Data(io_service& io_service, const ip::address& addr, const uint16_t port,
SimpleCallback* checkin, DNSLookup* lookup, DNSAnswer* answer) :
- io_(io_service), done_(false), stopped_by_hand_(false),
+ io_(io_service), done_(false),
checkin_callback_(checkin),lookup_callback_(lookup),
answer_callback_(answer)
{
@@ -80,7 +81,6 @@ struct UDPServer::Data {
*/
Data(const Data& other) :
io_(other.io_), socket_(other.socket_), done_(false),
- stopped_by_hand_(false),
checkin_callback_(other.checkin_callback_),
lookup_callback_(other.lookup_callback_),
answer_callback_(other.answer_callback_)
@@ -144,8 +144,6 @@ struct UDPServer::Data {
size_t bytes_;
bool done_;
- //whether user explicitly stop the server
- bool stopped_by_hand_;
// Callback functions provided by the caller
const SimpleCallback* checkin_callback_;
@@ -174,12 +172,6 @@ UDPServer::operator()(error_code ec, size_t length) {
/// a switch statement, inline variable declarations are not
/// permitted. Certain variables used below can be declared here.
- /// if user stopped the server, we won't enter the coroutine body
- /// just return
- if (data_->stopped_by_hand_) {
- return;
- }
-
CORO_REENTER (this) {
do {
/*
@@ -196,7 +188,9 @@ UDPServer::operator()(error_code ec, size_t length) {
CORO_YIELD data_->socket_->async_receive_from(
buffer(data_->data_.get(), MAX_LENGTH), *data_->sender_,
*this);
+
// Abort on fatal errors
+ // TODO: add log
if (ec) {
using namespace asio::error;
if (ec.value() != would_block && ec.value() != try_again &&
@@ -204,6 +198,7 @@ UDPServer::operator()(error_code ec, size_t length) {
return;
}
}
+
} while (ec || length == 0);
data_->bytes_ = length;
@@ -298,10 +293,14 @@ UDPServer::asyncLookup() {
/// Stop the UDPServer
void
UDPServer::stop() {
- //server should not be stopped twice
- if (data_->stopped_by_hand_)
- return;
- data_->stopped_by_hand_ = true;
+ /// Using close instead of cancel, because cancel
+ /// will only cancel the asynchornized event already submitted
+ /// to io service, the events post to io service after
+ /// cancel still can be scheduled by io service, if
+ /// the socket is cloesed, all the asynchronized event
+ /// for it won't be scheduled by io service not matter it is
+ /// submit to io serice before or after close call. And we will
+ //. get bad_descriptor error
data_->socket_->close();
}
diff --git a/src/lib/asiolink/udp_socket.h b/src/lib/asiolink/udp_socket.h
index bb94ad5..35fc7b1 100644
--- a/src/lib/asiolink/udp_socket.h
+++ b/src/lib/asiolink/udp_socket.h
@@ -28,7 +28,6 @@
#include <config.h>
-
#include <asiolink/io_asio_socket.h>
#include <asiolink/io_endpoint.h>
#include <asiolink/io_service.h>
@@ -49,20 +48,20 @@ private:
public:
enum {
- MAX_SIZE = 4096 // Send and receive size
+ MIN_SIZE = 4096 // Minimum send and receive size
};
-
+
/// \brief Constructor from an ASIO UDP socket.
///
- /// \param socket The ASIO representation of the UDP socket. It
- /// is assumed that the caller will open and close the socket, so
- /// these operations are a no-op for that socket.
+ /// \param socket The ASIO representation of the UDP socket. It is assumed
+ /// that the caller will open and close the socket, so these
+ /// operations are a no-op for that socket.
UDPSocket(asio::ip::udp::socket& socket);
/// \brief Constructor
///
/// Used when the UDPSocket is being asked to manage its own internal
- /// socket. It is assumed that open() and close() will not be used.
+ /// socket. In this case, the open() and close() methods are used.
///
/// \param service I/O Service object used to manage the socket.
UDPSocket(IOService& service);
@@ -70,68 +69,79 @@ public:
/// \brief Destructor
virtual ~UDPSocket();
- virtual int getNative() const { return (socket_.native()); }
- virtual int getProtocol() const { return (IPPROTO_UDP); }
+ /// \brief Return file descriptor of underlying socket
+ virtual int getNative() const {
+ return (socket_.native());
+ }
- /// \brief Open Socket
+ /// \brief Return protocol of socket
+ virtual int getProtocol() const {
+ return (IPPROTO_UDP);
+ }
+
+ /// \brief Is "open()" synchronous?
///
- /// Opens the UDP socket. In the model for transport-layer agnostic I/O,
- /// an "open" operation includes a connection to the remote end (which
- /// may take time). This does not happen for UDP, so the method returns
- /// "false" to indicate that the operation completed synchronously.
+ /// Indicates that the opening of a UDP socket is synchronous.
+ virtual bool isOpenSynchronous() const {
+ return true;
+ }
+
+ /// \brief Open Socket
///
- /// \param endpoint Endpoint to which the socket will connect to.
- /// \param callback Unused.
+ /// Opens the UDP socket. This is a synchronous operation.
///
- /// \return false to indicate that the "operation" completed synchronously.
- virtual bool open(const IOEndpoint* endpoint, C&);
+ /// \param endpoint Endpoint to which the socket will send data. This is
+ /// used to determine the address family trhat should be used for the
+ /// underlying socket.
+ /// \param callback Unused as the operation is synchronous.
+ virtual void open(const IOEndpoint* endpoint, C& callback);
/// \brief Send Asynchronously
///
- /// This corresponds to async_send_to() for UDP sockets and async_send()
- /// for TCP. In both cases an endpoint argument is supplied indicating the
- /// target of the send - this is ignored for TCP.
+ /// Calls the underlying socket's async_send_to() method to send a packet of
+ /// data asynchronously to the remote endpoint. The callback will be called
+ /// on completion.
///
/// \param data Data to send
/// \param length Length of data to send
/// \param endpoint Target of the send
/// \param callback Callback object.
virtual void asyncSend(const void* data, size_t length,
- const IOEndpoint* endpoint, C& callback);
+ const IOEndpoint* endpoint, C& callback);
/// \brief Receive Asynchronously
///
- /// This correstponds to async_receive_from() for UDP sockets and
- /// async_receive() for TCP. In both cases, an endpoint argument is
- /// supplied to receive the source of the communication. For TCP it will
- /// be filled in with details of the connection.
+ /// Calls the underlying socket's async_receive_from() method to read a
+ /// packet of data from a remote endpoint. Arrival of the data is signalled
+ /// via a call to the callback function.
///
/// \param data Buffer to receive incoming message
/// \param length Length of the data buffer
- /// \param cumulative Amount of data that should already be in the buffer.
- /// (This is ignored - every UPD receive fills the buffer from the start.)
+ /// \param offset Offset into buffer where data is to be put
/// \param endpoint Source of the communication
/// \param callback Callback object
- virtual void asyncReceive(void* data, size_t length, size_t cumulative,
- IOEndpoint* endpoint, C& callback);
+ virtual void asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback);
- /// \brief Checks if the data received is complete.
+ /// \brief Process received data
///
- /// As all the data is received in one I/O, so this is, this is effectively
- /// a no-op (although it does update the amount of data received).
+ /// See the description of IOAsioSocket::receiveComplete for a complete
+ /// description of this method.
///
- /// \param data Data buffer containing data to date. (This is ignored
- /// for UDP receives.)
- /// \param length Amount of data received in last asynchronous I/O
- /// \param cumulative On input, amount of data received before the last
- /// I/O. On output, the total amount of data received to date.
+ /// \param staging Pointer to the start of the staging buffer.
+ /// \param length Amount of data in the staging buffer.
+ /// \param cumulative Amount of data received before the staging buffer is
+ /// processed.
+ /// \param offset Unused.
+ /// \param expected unused.
+ /// \param outbuff Output buffer. Data in the staging buffer is be copied
+ /// to this output buffer in the call.
///
- /// \return true if the receive is complete, false if another receive is
- /// needed.
- virtual bool receiveComplete(void*, size_t length, size_t& cumulative) {
- cumulative = length;
- return (true);
- }
+ /// \return Always true
+ virtual bool processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff);
/// \brief Cancel I/O On Socket
virtual void cancel();
@@ -174,16 +184,16 @@ UDPSocket<C>::~UDPSocket()
delete socket_ptr_;
}
-// Open the socket. Throws an error on failure
-// TODO: Make the open more resilient
+// Open the socket.
-template <typename C> bool
+template <typename C> void
UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
- // Ignore opens on already-open socket. Don't throw a failure because
- // of uncertainties as to what precedes whan when using asynchronous I/O.
- // At also allows us a treat a passed-in socket as a self-managed socket.
-
+ // Ignore opens on already-open socket. (Don't throw a failure because
+ // of uncertainties as to what precedes whan when using asynchronous I/O.)
+ // It also allows us a treat a passed-in socket in exactly the same way as
+ // a self-managed socket (in that we can call the open() and close() methods
+ // of this class).
if (!isopen_) {
if (endpoint->getFamily() == AF_INET) {
socket_.open(asio::ip::udp::v4());
@@ -193,14 +203,21 @@ UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
}
isopen_ = true;
- // Ensure it can send and receive 4K buffers.
- socket_.set_option(asio::socket_base::send_buffer_size(MAX_SIZE));
- socket_.set_option(asio::socket_base::receive_buffer_size(MAX_SIZE));
- ;
- // Allow reuse of an existing port/address
- socket_.set_option(asio::socket_base::reuse_address(true));
+ // Ensure it can send and receive at least 4K buffers.
+ asio::ip::udp::socket::send_buffer_size snd_size;
+ socket_.get_option(snd_size);
+ if (snd_size.value() < MIN_SIZE) {
+ snd_size = MIN_SIZE;
+ socket_.set_option(snd_size);
+ }
+
+ asio::ip::udp::socket::receive_buffer_size rcv_size;
+ socket_.get_option(rcv_size);
+ if (rcv_size.value() < MIN_SIZE) {
+ rcv_size = MIN_SIZE;
+ socket_.set_option(rcv_size);
+ }
}
- return (false);
}
// Send a message. Should never do this if the socket is not open, so throw
@@ -208,19 +225,20 @@ UDPSocket<C>::open(const IOEndpoint* endpoint, C&) {
template <typename C> void
UDPSocket<C>::asyncSend(const void* data, size_t length,
- const IOEndpoint* endpoint, C& callback)
+ const IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
// Upconvert to a UDPEndpoint. We need to do this because although
// IOEndpoint is the base class of UDPEndpoint and TCPEndpoint, it
- // doing cont contain a method for getting at the underlying endpoint
- // type - those are in the derived class and the two classes differ on
+ // does not contain a method for getting at the underlying endpoint
+ // type - that is in the derived class and the two classes differ on
// return type.
-
assert(endpoint->getProtocol() == IPPROTO_UDP);
const UDPEndpoint* udp_endpoint =
static_cast<const UDPEndpoint*>(endpoint);
+
+ // ... and send the message.
socket_.async_send_to(asio::buffer(data, length),
udp_endpoint->getASIOEndpoint(), callback);
} else {
@@ -229,14 +247,12 @@ UDPSocket<C>::asyncSend(const void* data, size_t length,
}
}
-// Receive a message. Note that the "cumulative" argument is ignored - every UDP
-// receive is put into the buffer beginning at the start - there is no concept
-// receiving a subsequent part of a message. Same critera as before concerning
-// the need for the socket to be open.
+// Receive a message. Should never do this if the socket is not open, so throw
+// an exception if this is the case.
template <typename C> void
-UDPSocket<C>::asyncReceive(void* data, size_t length, size_t,
- IOEndpoint* endpoint, C& callback)
+UDPSocket<C>::asyncReceive(void* data, size_t length, size_t offset,
+ IOEndpoint* endpoint, C& callback)
{
if (isopen_) {
@@ -244,7 +260,15 @@ UDPSocket<C>::asyncReceive(void* data, size_t length, size_t,
assert(endpoint->getProtocol() == IPPROTO_UDP);
UDPEndpoint* udp_endpoint = static_cast<UDPEndpoint*>(endpoint);
- socket_.async_receive_from(asio::buffer(data, length),
+ // Ensure we can write into the buffer
+ if (offset >= length) {
+ isc_throw(BufferOverflow, "attempt to read into area beyond end of "
+ "UDP receive buffer");
+ }
+ void* buffer_start = static_cast<void*>(static_cast<uint8_t*>(data) + offset);
+
+ // Issue the read
+ socket_.async_receive_from(asio::buffer(buffer_start, length - offset),
udp_endpoint->getASIOEndpoint(), callback);
} else {
isc_throw(SocketNotOpen,
@@ -252,7 +276,29 @@ UDPSocket<C>::asyncReceive(void* data, size_t length, size_t,
}
}
+// Receive complete. Just copy the data across to the output buffer and
+// update arguments as appropriate.
+
+template <typename C> bool
+UDPSocket<C>::processReceivedData(const void* staging, size_t length,
+ size_t& cumulative, size_t& offset,
+ size_t& expected,
+ isc::dns::OutputBufferPtr& outbuff)
+{
+ // Set return values to what we should expect.
+ cumulative = length;
+ expected = length;
+ offset = 0;
+
+ // Copy data across
+ outbuff->writeData(staging, length);
+
+ // ... and mark that we have everything.
+ return (true);
+}
+
// Cancel I/O on the socket. No-op if the socket is not open.
+
template <typename C> void
UDPSocket<C>::cancel() {
if (isopen_) {
diff --git a/src/lib/cache/Makefile.am b/src/lib/cache/Makefile.am
index 264aca6..107fc9a 100644
--- a/src/lib/cache/Makefile.am
+++ b/src/lib/cache/Makefile.am
@@ -29,5 +29,6 @@ libcache_la_SOURCES += rrset_entry.h rrset_entry.cc
libcache_la_SOURCES += cache_entry_key.h cache_entry_key.cc
libcache_la_SOURCES += rrset_copy.h rrset_copy.cc
libcache_la_SOURCES += local_zone_data.h local_zone_data.cc
+libcache_la_SOURCES += message_utility.h message_utility.cc
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/cache/TODO b/src/lib/cache/TODO
index a7d2458..aa7e3b0 100644
--- a/src/lib/cache/TODO
+++ b/src/lib/cache/TODO
@@ -11,4 +11,8 @@
to expire.
* When the rrset beging updated is an NS rrset, NSAS should be updated
together.
+* Share the NXDOMAIN info between different type queries. current implementation
+ can only cache for the type that user quired, for example, if user query A
+ record of a.example. and the server replied with NXDOMAIN, this should be
+ cached for all the types queries of a.example.
diff --git a/src/lib/cache/message_cache.cc b/src/lib/cache/message_cache.cc
index f1334a2..0464f87 100644
--- a/src/lib/cache/message_cache.cc
+++ b/src/lib/cache/message_cache.cc
@@ -18,25 +18,34 @@
#include <nsas/hash_table.h>
#include <nsas/hash_deleter.h>
#include "message_cache.h"
+#include "message_utility.h"
#include "cache_entry_key.h"
+namespace isc {
+namespace cache {
+
using namespace isc::nsas;
using namespace isc::dns;
using namespace std;
+using namespace MessageUtility;
-namespace isc {
-namespace cache {
-
-MessageCache::MessageCache(boost::shared_ptr<RRsetCache> rrset_cache,
- uint32_t cache_size, uint16_t message_class):
+MessageCache::MessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache):
message_class_(message_class),
rrset_cache_(rrset_cache),
+ negative_soa_cache_(negative_soa_cache),
message_table_(new NsasEntryCompare<MessageEntry>, cache_size),
message_lru_((3 * cache_size),
new HashDeleter<MessageEntry>(message_table_))
{
}
+MessageCache::~MessageCache() {
+ // Destroy all the message entries in the cache.
+ message_lru_.clear();
+}
+
bool
MessageCache::lookup(const isc::dns::Name& qname,
const isc::dns::RRType& qtype,
@@ -46,8 +55,16 @@ MessageCache::lookup(const isc::dns::Name& qname,
HashKey entry_key = HashKey(entry_name, RRClass(message_class_));
MessageEntryPtr msg_entry = message_table_.get(entry_key);
if(msg_entry) {
- message_lru_.touch(msg_entry);
- return (msg_entry->genMessage(time(NULL), response));
+ // Check whether the message entry has expired.
+ if (msg_entry->getExpireTime() > time(NULL)) {
+ message_lru_.touch(msg_entry);
+ return (msg_entry->genMessage(time(NULL), response));
+ } else {
+ // message entry expires, remove it from hash table and lru list.
+ message_table_.remove(entry_key);
+ message_lru_.remove(msg_entry);
+ return (false);
+ }
}
return (false);
@@ -55,8 +72,13 @@ MessageCache::lookup(const isc::dns::Name& qname,
bool
MessageCache::update(const Message& msg) {
+ if (!canMessageBeCached(msg)){
+ return (false);
+ }
+
QuestionIterator iter = msg.beginQuestion();
- std::string entry_name = genCacheEntryName((*iter)->getName(), (*iter)->getType());
+ std::string entry_name = genCacheEntryName((*iter)->getName(),
+ (*iter)->getType());
HashKey entry_key = HashKey(entry_name, RRClass(message_class_));
// The simplest way to update is removing the old message entry directly.
@@ -69,7 +91,8 @@ MessageCache::update(const Message& msg) {
message_lru_.remove(old_msg_entry);
}
- MessageEntryPtr msg_entry(new MessageEntry(msg, rrset_cache_));
+ MessageEntryPtr msg_entry(new MessageEntry(msg, rrset_cache_,
+ negative_soa_cache_));
message_lru_.add(msg_entry);
return (message_table_.add(msg_entry, entry_key, true));
}
diff --git a/src/lib/cache/message_cache.h b/src/lib/cache/message_cache.h
index 65c7381..790ed83 100644
--- a/src/lib/cache/message_cache.h
+++ b/src/lib/cache/message_cache.h
@@ -21,12 +21,11 @@
#include "message_entry.h"
#include <nsas/hash_table.h>
#include <nsas/lru_list.h>
+#include "rrset_cache.h"
namespace isc {
namespace cache {
-class RRsetCache;
-
/// \brief Message Cache
/// The object of MessageCache represents the cache for class-specific
/// messages.
@@ -37,12 +36,18 @@ private:
MessageCache(const MessageCache& source);
MessageCache& operator=(const MessageCache& source);
public:
+ /// \param rrset_cache The cache that stores the RRsets that the
+ /// message entry will points to
/// \param cache_size The size of message cache.
- MessageCache(boost::shared_ptr<RRsetCache> rrset_cache_,
- uint32_t cache_size, uint16_t message_class);
+ /// \param message_class The class of the message cache
+ /// \param negative_soa_cache The cache that stores the SOA record
+ /// that comes from negative response message
+ MessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache);
/// \brief Destructor function
- virtual ~MessageCache() {}
+ virtual ~MessageCache();
/// \brief Look up message in cache.
/// \param message generated response message if the message entry
@@ -84,7 +89,8 @@ protected:
// Make these variants be protected for easy unittest.
protected:
uint16_t message_class_; // The class of the message cache.
- boost::shared_ptr<RRsetCache> rrset_cache_;
+ RRsetCachePtr rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
isc::nsas::HashTable<MessageEntry> message_table_;
isc::nsas::LruList<MessageEntry> message_lru_;
};
diff --git a/src/lib/cache/message_entry.cc b/src/lib/cache/message_entry.cc
index 6396167..de4ea89 100644
--- a/src/lib/cache/message_entry.cc
+++ b/src/lib/cache/message_entry.cc
@@ -18,6 +18,7 @@
#include <dns/message.h>
#include <nsas/nsas_entry.h>
#include "message_entry.h"
+#include "message_utility.h"
#include "rrset_cache.h"
using namespace isc::dns;
@@ -56,9 +57,27 @@ namespace cache {
static uint32_t MAX_UINT32 = numeric_limits<uint32_t>::max();
+// As with caching positive responses it is sensible for a resolver to
+// limit for how long it will cache a negative response as the protocol
+// supports caching for up to 68 years. Such a limit should not be
+// greater than that applied to positive answers and preferably be
+// tunable. Values of one to three hours have been found to work well
+// and would make sensible a default. Values exceeding one day have
+// been found to be problematic. (sec 5, RFC2308)
+// The default value is 3 hourse (10800 seconds)
+// TODO:Give an option to let user configure
+static uint32_t MAX_NEGATIVE_CACHE_TTL = 10800;
+
+// Sets the maximum time for which the server will cache ordinary (positive) answers. The
+// default is one week (7 days = 604800 seconds)
+// TODO:Give an option to let user configure
+static uint32_t MAX_NORMAL_CACHE_TTL = 604800;
+
MessageEntry::MessageEntry(const isc::dns::Message& msg,
- boost::shared_ptr<RRsetCache> rrset_cache):
+ const RRsetCachePtr& rrset_cache,
+ const RRsetCachePtr& negative_soa_cache):
rrset_cache_(rrset_cache),
+ negative_soa_cache_(negative_soa_cache),
headerflag_aa_(false),
headerflag_tc_(false)
{
@@ -74,7 +93,8 @@ MessageEntry::getRRsetEntries(vector<RRsetEntryPtr>& rrset_entry_vec,
uint16_t entry_count = answer_count_ + authority_count_ + additional_count_;
rrset_entry_vec.reserve(rrset_entry_vec.size() + entry_count);
for (int index = 0; index < entry_count; ++index) {
- RRsetEntryPtr rrset_entry = rrset_cache_->lookup(rrsets_[index].name_,
+ RRsetCache* rrset_cache = rrsets_[index].cache_;
+ RRsetEntryPtr rrset_entry = rrset_cache->lookup(rrsets_[index].name_,
rrsets_[index].type_);
if (rrset_entry && time_now < rrset_entry->getExpireTime()) {
rrset_entry_vec.push_back(rrset_entry);
@@ -104,8 +124,9 @@ MessageEntry::addRRset(isc::dns::Message& message,
end_index = start_index + additional_count_;
}
- for(uint16_t index = start_index; index < end_index; ++index) {
- message.addRRset(section, rrset_entry_vec[index]->getRRset(), dnssec_need);
+ for (uint16_t index = start_index; index < end_index; ++index) {
+ message.addRRset(section, rrset_entry_vec[index]->getRRset(),
+ dnssec_need);
}
}
@@ -127,7 +148,9 @@ MessageEntry::genMessage(const time_t& time_now,
// Begin message generation. We don't need to add question
// section, since it has been included in the message.
// Set cached header flags.
- msg.setHeaderFlag(Message::HEADERFLAG_AA, headerflag_aa_);
+ // The AA flag bit should be cleared because this is a response from
+ // resolver cache
+ msg.setHeaderFlag(Message::HEADERFLAG_AA, false);
msg.setHeaderFlag(Message::HEADERFLAG_TC, headerflag_tc_);
bool dnssec_need = msg.getEDNS().get();
@@ -233,7 +256,8 @@ MessageEntry::parseSection(const isc::dns::Message& msg,
RRsetPtr rrset_ptr = *iter;
RRsetTrustLevel level = getRRsetTrustLevel(msg, rrset_ptr, section);
RRsetEntryPtr rrset_entry = rrset_cache_->update(*rrset_ptr, level);
- rrsets_.push_back(RRsetRef(rrset_ptr->getName(), rrset_ptr->getType()));
+ rrsets_.push_back(RRsetRef(rrset_ptr->getName(), rrset_ptr->getType(),
+ rrset_cache_.get()));
uint32_t rrset_ttl = rrset_entry->getTTL();
if (smaller_ttl > rrset_ttl) {
@@ -247,6 +271,37 @@ MessageEntry::parseSection(const isc::dns::Message& msg,
}
void
+MessageEntry::parseNegativeResponseAuthoritySection(const isc::dns::Message& msg,
+ uint32_t& min_ttl,
+ uint16_t& rrset_count)
+{
+ uint16_t count = 0;
+ for (RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ iter != msg.endSection(Message::SECTION_AUTHORITY);
+ ++iter) {
+ RRsetPtr rrset_ptr = *iter;
+ RRsetTrustLevel level = getRRsetTrustLevel(msg, rrset_ptr,
+ Message::SECTION_AUTHORITY);
+ boost::shared_ptr<RRsetCache> rrset_cache_ptr = rrset_cache_;
+ if (rrset_ptr->getType() == RRType::SOA()) {
+ rrset_cache_ptr = negative_soa_cache_;
+ }
+
+ RRsetEntryPtr rrset_entry = rrset_cache_ptr->update(*rrset_ptr, level);
+ rrsets_.push_back(RRsetRef(rrset_ptr->getName(),
+ rrset_ptr->getType(),
+ rrset_cache_ptr.get()));
+ uint32_t rrset_ttl = rrset_entry->getTTL();
+ if (min_ttl > rrset_ttl) {
+ min_ttl = rrset_ttl;
+ }
+ ++count;
+ }
+
+ rrset_count = count;
+}
+
+void
MessageEntry::initMessageEntry(const isc::dns::Message& msg) {
//TODO better way to cache the header flags?
headerflag_aa_ = msg.getHeaderFlag(Message::HEADERFLAG_AA);
@@ -261,14 +316,30 @@ MessageEntry::initMessageEntry(const isc::dns::Message& msg) {
query_class_ = (*iter)->getClass().getCode();
uint32_t min_ttl = MAX_UINT32;
+
+ bool isNegativeResponse = MessageUtility::isNegativeResponse(msg);
+
parseSection(msg, Message::SECTION_ANSWER, min_ttl, answer_count_);
- parseSection(msg, Message::SECTION_AUTHORITY, min_ttl, authority_count_);
+ if (!isNegativeResponse) {
+ parseSection(msg, Message::SECTION_AUTHORITY, min_ttl, authority_count_);
+ } else {
+ parseNegativeResponseAuthoritySection(msg, min_ttl, authority_count_);
+ }
parseSection(msg, Message::SECTION_ADDITIONAL, min_ttl, additional_count_);
+ // Limit the ttl to a prset max-value
+ if (!isNegativeResponse) {
+ if (min_ttl > MAX_NORMAL_CACHE_TTL) {
+ min_ttl = MAX_NORMAL_CACHE_TTL;
+ }
+ } else {
+ if (min_ttl > MAX_NEGATIVE_CACHE_TTL) {
+ min_ttl = MAX_NEGATIVE_CACHE_TTL;
+ }
+ }
+
expire_time_ = time(NULL) + min_ttl;
}
} // namespace cache
} // namespace isc
-
-
diff --git a/src/lib/cache/message_entry.h b/src/lib/cache/message_entry.h
index 682c171..6775ff6 100644
--- a/src/lib/cache/message_entry.h
+++ b/src/lib/cache/message_entry.h
@@ -19,33 +19,15 @@
#include <dns/message.h>
#include <dns/rrset.h>
#include <nsas/nsas_entry.h>
+#include "rrset_cache.h"
#include "rrset_entry.h"
-
using namespace isc::nsas;
namespace isc {
namespace cache {
class RRsetEntry;
-class RRsetCache;
-
-/// \brief Information to refer an RRset.
-///
-/// There is no class information here, since the rrsets are cached in
-/// the class-specific rrset cache.
-struct RRsetRef{
- /// \brief Constructor
- ///
- /// \param name The Name for the RRset
- /// \param type the RRType for the RRrset
- RRsetRef(const isc::dns::Name& name, const isc::dns::RRType& type):
- name_(name), type_(type)
- {}
-
- isc::dns::Name name_; // Name of rrset.
- isc::dns::RRType type_; // Type of rrset.
-};
/// \brief Message Entry
///
@@ -56,6 +38,27 @@ class MessageEntry : public NsasEntry<MessageEntry> {
private:
MessageEntry(const MessageEntry& source);
MessageEntry& operator=(const MessageEntry& source);
+
+ /// \brief Information to refer an RRset.
+ ///
+ /// There is no class information here, since the rrsets are cached in
+ /// the class-specific rrset cache.
+ struct RRsetRef{
+ /// \brief Constructor
+ ///
+ /// \param name The Name for the RRset
+ /// \param type The RRType for the RRrset
+ /// \param cache Which cache the RRset is stored in
+ RRsetRef(const isc::dns::Name& name, const isc::dns::RRType& type,
+ RRsetCache* cache):
+ name_(name), type_(type), cache_(cache)
+ {}
+
+ isc::dns::Name name_; // Name of rrset.
+ isc::dns::RRType type_; // Type of rrset.
+ RRsetCache* cache_; //Which cache the RRset is stored
+ };
+
public:
/// \brief Initialize the message entry object with one dns
@@ -66,8 +69,14 @@ public:
/// since some new rrset entries may be inserted into
/// rrset cache, or the existed rrset entries need
/// to be updated.
+ /// \param negative_soa_cache the pointer of RRsetCache. This
+ /// cache is used only for storing SOA rrset from negative
+ /// response (NXDOMAIN or NOERROR_NODATA)
MessageEntry(const isc::dns::Message& message,
- boost::shared_ptr<RRsetCache> rrset_cache);
+ const RRsetCachePtr& rrset_cache,
+ const RRsetCachePtr& negative_soa_cache);
+
+ ~MessageEntry() { delete hash_key_ptr_; };
/// \brief generate one dns message according
/// the rrsets information of the message.
@@ -87,6 +96,12 @@ public:
return (*hash_key_ptr_);
}
+ /// \brief Get expire time of the message entry.
+ /// \return return the expire time of message entry.
+ time_t getExpireTime() const {
+ return (expire_time_);
+ }
+
/// \short Protected memebers, so they can be accessed by tests.
//@{
protected:
@@ -109,6 +124,16 @@ protected:
uint32_t& smaller_ttl,
uint16_t& rrset_count);
+ /// \brief Parse the RRsets in the authority section of
+ /// negative response. The SOA RRset need to be located and
+ /// stored in a seperate cache
+ /// \param msg The message to parse the RRsets from
+ /// \param min_ttl Get the minimum ttl of rrset in the authority section
+ /// \param rrset_count the rrset count of the authority section
+ void parseNegativeResponseAuthoritySection(const isc::dns::Message& msg,
+ uint32_t& min_ttl,
+ uint16_t& rrset_count);
+
/// \brief Get RRset Trustworthiness
/// The algorithm refers to RFC2181 section 5.4.1
/// Only the rrset can be updated by the rrsets
@@ -153,7 +178,9 @@ private:
HashKey* hash_key_ptr_; // the key for messag entry in hash table.
std::vector<RRsetRef> rrsets_;
- boost::shared_ptr<RRsetCache> rrset_cache_;
+ RRsetCachePtr rrset_cache_; //Normal rrset cache
+ // SOA rrset from negative response
+ RRsetCachePtr negative_soa_cache_;
std::string query_name_; // query name of the message.
uint16_t query_class_; // query class of the message.
diff --git a/src/lib/cache/message_utility.cc b/src/lib/cache/message_utility.cc
new file mode 100644
index 0000000..53a3352
--- /dev/null
+++ b/src/lib/cache/message_utility.cc
@@ -0,0 +1,80 @@
+// 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.
+
+// $Id$
+
+#include "message_utility.h"
+#include <dns/rcode.h>
+
+using namespace isc::dns;
+
+namespace isc {
+namespace cache {
+namespace MessageUtility{
+
+bool
+hasTheRecordInAuthoritySection(const isc::dns::Message& msg,
+ const isc::dns::RRType& type)
+{
+ // isc::dns::Message provide one function hasRRset() should be used to
+ // determine whether the given section has an RRset matching the given
+ // name and type, but currently it is not const-qualified and cannot be
+ // used here
+ // TODO: use hasRRset() function when it is const qualified
+ for (RRsetIterator iter = msg.beginSection(Message::SECTION_AUTHORITY);
+ iter != msg.endSection(Message::SECTION_AUTHORITY);
+ ++iter) {
+ RRsetPtr rrset_ptr = *iter;
+ if (rrset_ptr->getType() == type) {
+ return (true);
+ }
+ }
+ return (false);
+}
+
+bool
+isNegativeResponse(const isc::dns::Message& msg) {
+ if (msg.getRcode() == Rcode::NXDOMAIN()) {
+ return (true);
+ } else if (msg.getRcode() == Rcode::NOERROR()) {
+ // no data in the answer section
+ if (msg.getRRCount(Message::SECTION_ANSWER) == 0) {
+ // NODATA type 1/ type 2 (ref sec2.2 of RFC2308)
+ if (hasTheRecordInAuthoritySection(msg, RRType::SOA())) {
+ return (true);
+ } else if (!hasTheRecordInAuthoritySection(msg, RRType::NS())) {
+ // NODATA type 3 (sec2.2 of RFC2308)
+ return (true);
+ }
+ }
+ }
+
+ return (false);
+}
+
+bool
+canMessageBeCached(const isc::dns::Message& msg) {
+ // If the message is a negative response, but no SOA record is found in
+ // the authority section, the message cannot be cached
+ if (isNegativeResponse(msg) &&
+ !hasTheRecordInAuthoritySection(msg, RRType::SOA())){
+ return (false);
+ }
+
+ return (true);
+}
+
+} // namespace MessageUtility
+} // namespace cache
+} // namespace isc
diff --git a/src/lib/cache/message_utility.h b/src/lib/cache/message_utility.h
new file mode 100644
index 0000000..a77af07
--- /dev/null
+++ b/src/lib/cache/message_utility.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.
+
+// $Id$
+
+#ifndef __MESSAGE_UTILITY_H
+#define __MESSAGE_UTILITY_H
+
+#include <dns/message.h>
+
+namespace isc {
+namespace cache {
+
+/// \brief Some utility functions to extract info from message
+///
+/// We need to check the message before cache it, for example, if no SOA
+/// record is found in the Authority section of NXDOMAIN response, the
+/// message cannot be cached
+namespace MessageUtility{
+
+/// \brief Check whether there is some type of record in
+/// Authority section
+///
+/// \param msg The response message to be checked
+/// \param type The RR type that need to check
+bool hasTheRecordInAuthoritySection(const isc::dns::Message& msg,
+ const isc::dns::RRType& type);
+
+/// \brief Check whetehr the message is a negative response
+/// (NXDOMAIN or NOERROR_NODATA)
+///
+/// \param msg The response message
+bool isNegativeResponse(const isc::dns::Message& msg);
+
+/// \brief Check whether the message can be cached
+/// Negative responses without SOA records SHOULD NOT be cached as there
+/// is no way to prevent the negative responses looping forever between a
+/// pair of servers even with a short TTL.
+/// Despite the DNS forming a tree of servers, with various mis-
+/// configurations it is possible to form a loop in the query graph, e.g.
+/// two servers listing each other as forwarders, various lame server
+/// configurations. Without a TTL count down a cache negative response
+/// when received by the next server would have its TTL reset. This
+/// negative indication could then live forever circulating between the
+/// servers involved. (Sec 5, RFC2308)
+///
+/// \param msg The response message
+bool canMessageBeCached(const isc::dns::Message& msg);
+
+} // namespace MessageUtility
+} // namespace cache
+} // namespace isc
+
+
+#endif//__MESSAGE_UTILITY_H
diff --git a/src/lib/cache/resolver_cache.cc b/src/lib/cache/resolver_cache.cc
index 0734da8..261db3c 100644
--- a/src/lib/cache/resolver_cache.cc
+++ b/src/lib/cache/resolver_cache.cc
@@ -32,12 +32,17 @@ ResolverClassCache::ResolverClassCache(const RRClass& cache_class) :
local_zone_data_ = LocalZoneDataPtr(new LocalZoneData(cache_class_.getCode()));
rrsets_cache_ = RRsetCachePtr(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE,
cache_class_.getCode()));
+ // SOA rrset cache from negative response
+ negative_soa_cache_ = RRsetCachePtr(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE,
+ cache_class_.getCode()));
+
messages_cache_ = MessageCachePtr(new MessageCache(rrsets_cache_,
MESSAGE_CACHE_DEFAULT_SIZE,
- cache_class_.getCode()));
+ cache_class_.getCode(),
+ negative_soa_cache_));
}
-ResolverClassCache::ResolverClassCache(CacheSizeInfo cache_info) :
+ResolverClassCache::ResolverClassCache(const CacheSizeInfo& cache_info) :
cache_class_(cache_info.cclass)
{
uint16_t klass = cache_class_.getCode();
@@ -45,14 +50,18 @@ ResolverClassCache::ResolverClassCache(CacheSizeInfo cache_info) :
local_zone_data_ = LocalZoneDataPtr(new LocalZoneData(klass));
rrsets_cache_ = RRsetCachePtr(new
RRsetCache(cache_info.rrset_cache_size, klass));
+ // SOA rrset cache from negative response
+ negative_soa_cache_ = RRsetCachePtr(new RRsetCache(cache_info.rrset_cache_size,
+ klass));
+
messages_cache_ = MessageCachePtr(new MessageCache(rrsets_cache_,
cache_info.message_cache_size,
- klass));
+ klass, negative_soa_cache_));
}
const RRClass&
ResolverClassCache::getClass() const {
- return cache_class_;
+ return (cache_class_);
}
bool
@@ -104,7 +113,7 @@ ResolverClassCache::update(const isc::dns::Message& msg) {
}
bool
-ResolverClassCache::updateRRsetCache(const isc::dns::ConstRRsetPtr rrset_ptr,
+ResolverClassCache::updateRRsetCache(const isc::dns::ConstRRsetPtr& rrset_ptr,
RRsetCachePtr rrset_cache_ptr)
{
RRsetTrustLevel level;
@@ -120,7 +129,7 @@ ResolverClassCache::updateRRsetCache(const isc::dns::ConstRRsetPtr rrset_ptr,
}
bool
-ResolverClassCache::update(const isc::dns::ConstRRsetPtr rrset_ptr) {
+ResolverClassCache::update(const isc::dns::ConstRRsetPtr& rrset_ptr) {
// First update local zone, then update rrset cache.
local_zone_data_->update((*rrset_ptr.get()));
updateRRsetCache(rrset_ptr, rrsets_cache_);
@@ -209,7 +218,7 @@ ResolverCache::update(const isc::dns::Message& msg) {
}
bool
-ResolverCache::update(const isc::dns::ConstRRsetPtr rrset_ptr) {
+ResolverCache::update(const isc::dns::ConstRRsetPtr& rrset_ptr) {
ResolverClassCache* cc = getClassCache(rrset_ptr->getClass());
if (cc) {
return (cc->update(rrset_ptr));
@@ -232,10 +241,10 @@ ResolverClassCache*
ResolverCache::getClassCache(const isc::dns::RRClass& cache_class) const {
for (int i = 0; i < class_caches_.size(); ++i) {
if (class_caches_[i]->getClass() == cache_class) {
- return class_caches_[i];
+ return (class_caches_[i]);
}
}
- return NULL;
+ return (NULL);
}
} // namespace cache
diff --git a/src/lib/cache/resolver_cache.h b/src/lib/cache/resolver_cache.h
index a8149e4..49818b5 100644
--- a/src/lib/cache/resolver_cache.h
+++ b/src/lib/cache/resolver_cache.h
@@ -32,6 +32,7 @@ class RRsetCache;
//TODO a better proper default cache size
#define MESSAGE_CACHE_DEFAULT_SIZE 10000
#define RRSET_CACHE_DEFAULT_SIZE 20000
+#define NEGATIVE_RRSET_CACHE_DEFAULT_SIZE 10000
/// \brief Cache Size Information.
///
@@ -44,7 +45,7 @@ public:
/// \param cls The RRClass code
/// \param msg_cache_size The size for the message cache
/// \param rst_cache_size The size for the RRset cache
- CacheSizeInfo(const isc::dns::RRClass& cls,
+ CacheSizeInfo(const isc::dns::RRClass& cls,
uint32_t msg_cache_size,
uint32_t rst_cache_size):
cclass(cls),
@@ -87,7 +88,7 @@ public:
/// \brief Construct Function.
/// \param caches_size cache size information for each
/// messages/rrsets of different classes.
- ResolverClassCache(CacheSizeInfo cache_info);
+ ResolverClassCache(const CacheSizeInfo& cache_info);
/// \name Lookup Interfaces
//@{
@@ -132,6 +133,11 @@ public:
/// \note the function doesn't do any message validation check,
/// the user should make sure the message is valid, and of
/// the right class
+ /// TODO: Share the NXDOMAIN info between different type queries
+ /// current implementation can only cache for the type that
+ /// user quired, for example, if user query A record of
+ /// a.example. and the server replied with NXDOMAIN, this
+ /// should be cached for all the types queries of a.example.
bool update(const isc::dns::Message& msg);
/// \brief Update the rrset in the cache with the new one.
@@ -149,13 +155,13 @@ public:
///
/// \note The class of the RRset must have been checked. It is not
/// here.
- bool update(const isc::dns::ConstRRsetPtr rrset_ptr);
+ bool update(const isc::dns::ConstRRsetPtr& rrset_ptr);
/// \brief Get the RRClass this cache is for
///
/// \return The RRClass of this cache
const isc::dns::RRClass& getClass() const;
-
+
private:
/// \brief Update rrset cache.
///
@@ -165,7 +171,7 @@ private:
/// \return return true if the rrset is updated in the rrset cache,
/// or else return false if failed.
/// \param rrset_cache_ptr The rrset cache need to be updated.
- bool updateRRsetCache(const isc::dns::ConstRRsetPtr rrset_ptr,
+ bool updateRRsetCache(const isc::dns::ConstRRsetPtr& rrset_ptr,
RRsetCachePtr rrset_cache_ptr);
/// \brief Class this cache is for.
@@ -181,10 +187,13 @@ private:
/// Cache for rrsets in local zones, rrsets
/// in it never expire.
LocalZoneDataPtr local_zone_data_;
+ //@}
/// \brief cache the rrsets parsed from the received message.
RRsetCachePtr rrsets_cache_;
- //@}
+
+ /// \brief cache the SOA rrset parsed from the negative response message.
+ RRsetCachePtr negative_soa_cache_;
};
class ResolverCache {
@@ -289,7 +298,7 @@ public:
///
/// \overload
///
- bool update(const isc::dns::ConstRRsetPtr rrset_ptr);
+ bool update(const isc::dns::ConstRRsetPtr& rrset_ptr);
/// \name Cache Serialization
//@{
diff --git a/src/lib/cache/rrset_cache.cc b/src/lib/cache/rrset_cache.cc
index 7dab3b5..f538320 100644
--- a/src/lib/cache/rrset_cache.cc
+++ b/src/lib/cache/rrset_cache.cc
@@ -42,45 +42,41 @@ RRsetCache::lookup(const isc::dns::Name& qname,
{
const string entry_name = genCacheEntryName(qname, qtype);
RRsetEntryPtr entry_ptr = rrset_table_.get(HashKey(entry_name, RRClass(class_)));
-
- //If the rrset entry has expired, return NULL.
- if(entry_ptr && (time(NULL) > entry_ptr->getExpireTime())) {
- return (RRsetEntryPtr());
+ if (entry_ptr) {
+ if (entry_ptr->getExpireTime() > time(NULL)) {
+ // Only touch the non-expired rrset entries
+ rrset_lru_.touch(entry_ptr);
+ return (entry_ptr);
+ } else {
+ // the rrset entry has expired, so just remove it from
+ // hash table and lru list.
+ rrset_table_.remove(entry_ptr->hashKey());
+ rrset_lru_.remove(entry_ptr);
+ }
}
- return (entry_ptr);
+
+ return (RRsetEntryPtr());
}
RRsetEntryPtr
RRsetCache::update(const isc::dns::RRset& rrset, const RRsetTrustLevel& level) {
// TODO: If the RRset is an NS, we should update the NSAS as well
-
// lookup first
RRsetEntryPtr entry_ptr = lookup(rrset.getName(), rrset.getType());
- if(!entry_ptr) {
- // rrset entry doesn't exist, create one rrset entry for the rrset
- // and add it directly.
- entry_ptr.reset(new RRsetEntry(rrset, level));
- // Replace the expired rrset entry if it exists.
- rrset_table_.add(entry_ptr, entry_ptr->hashKey(), true);
- //TODO , lru list touch.
- return (entry_ptr);
- } else {
- // there is one rrset entry in the cache, need to check whether
- // the new rrset is more authoritative.
+ if (entry_ptr) {
if (entry_ptr->getTrustLevel() > level) {
- // existed rrset entry is more authoritative, do nothing,
- // just return it.
- //TODO, lru list touch
+ // existed rrset entry is more authoritative, just return it
return (entry_ptr);
} else {
- HashKey key = entry_ptr->hashKey();
- entry_ptr.reset(new RRsetEntry(rrset, level));
- //TODO, lru list touch.
- // Replace the expired rrset entry if it exists.
- rrset_table_.add(entry_ptr, entry_ptr->hashKey(), true);
- return (entry_ptr);
+ // Remove the old rrset entry from the lru list.
+ rrset_lru_.remove(entry_ptr);
}
}
+
+ entry_ptr.reset(new RRsetEntry(rrset, level));
+ rrset_table_.add(entry_ptr, entry_ptr->hashKey(), true);
+ rrset_lru_.add(entry_ptr);
+ return (entry_ptr);
}
#if 0
diff --git a/src/lib/cache/rrset_cache.h b/src/lib/cache/rrset_cache.h
index 5bf2730..104c2a5 100644
--- a/src/lib/cache/rrset_cache.h
+++ b/src/lib/cache/rrset_cache.h
@@ -40,12 +40,14 @@ private:
RRsetCache(const RRsetCache&);
RRsetCache& operator=(const RRsetCache&);
public:
- /// \brief Constructor
+ /// \brief Constructor and Destructor
///
/// \param cache_size the size of rrset cache.
/// \param rrset_class the class of rrset cache.
RRsetCache(uint32_t cache_size, uint16_t rrset_class);
- virtual ~RRsetCache() {}
+ virtual ~RRsetCache() {
+ rrset_lru_.clear(); // Clear the rrset entries in the list.
+ }
//@}
/// \brief Look up rrset in cache.
diff --git a/src/lib/cache/tests/Makefile.am b/src/lib/cache/tests/Makefile.am
index 6f05f74..4763f55 100644
--- a/src/lib/cache/tests/Makefile.am
+++ b/src/lib/cache/tests/Makefile.am
@@ -38,6 +38,7 @@ run_unittests_SOURCES += message_cache_unittest.cc
run_unittests_SOURCES += message_entry_unittest.cc
run_unittests_SOURCES += local_zone_data_unittest.cc
run_unittests_SOURCES += resolver_cache_unittest.cc
+run_unittests_SOURCES += negative_cache_unittest.cc
run_unittests_SOURCES += cache_test_messagefromfile.h
run_unittests_SOURCES += cache_test_sectioncount.h
@@ -53,12 +54,15 @@ endif
run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
noinst_PROGRAMS = $(TESTS)
-EXTRA_DIST = testdata/message_fromWire1
+EXTRA_DIST = testdata/message_cname_referral.wire
+EXTRA_DIST += testdata/message_example_com_soa.wire
+EXTRA_DIST += testdata/message_fromWire1
EXTRA_DIST += testdata/message_fromWire2
EXTRA_DIST += testdata/message_fromWire3
EXTRA_DIST += testdata/message_fromWire4
@@ -66,3 +70,11 @@ EXTRA_DIST += testdata/message_fromWire5
EXTRA_DIST += testdata/message_fromWire6
EXTRA_DIST += testdata/message_fromWire7
EXTRA_DIST += testdata/message_fromWire8
+EXTRA_DIST += testdata/message_fromWire9
+EXTRA_DIST += testdata/message_large_ttl.wire
+EXTRA_DIST += testdata/message_nodata_with_soa.wire
+EXTRA_DIST += testdata/message_nxdomain_cname.wire
+EXTRA_DIST += testdata/message_nxdomain_large_ttl.wire
+EXTRA_DIST += testdata/message_nxdomain_no_soa.wire
+EXTRA_DIST += testdata/message_nxdomain_with_soa.wire
+EXTRA_DIST += testdata/message_referral.wire
diff --git a/src/lib/cache/tests/message_cache_unittest.cc b/src/lib/cache/tests/message_cache_unittest.cc
index b2c0cd3..fc62e21 100644
--- a/src/lib/cache/tests/message_cache_unittest.cc
+++ b/src/lib/cache/tests/message_cache_unittest.cc
@@ -33,9 +33,10 @@ namespace {
/// its internals.
class DerivedMessageCache: public MessageCache {
public:
- DerivedMessageCache(boost::shared_ptr<RRsetCache> rrset_cache_,
- uint32_t cache_size, uint16_t message_class):
- MessageCache(rrset_cache_, cache_size, message_class)
+ DerivedMessageCache(const RRsetCachePtr& rrset_cache,
+ uint32_t cache_size, uint16_t message_class,
+ const RRsetCachePtr& negative_soa_cache):
+ MessageCache(rrset_cache, cache_size, message_class, negative_soa_cache)
{}
uint16_t messages_count() {
@@ -70,20 +71,33 @@ public:
{
uint16_t class_ = RRClass::IN().getCode();
rrset_cache_.reset(new DerivedRRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_));
- message_cache_.reset(new DerivedMessageCache(rrset_cache_,
- MESSAGE_CACHE_DEFAULT_SIZE, class_ ));
+ negative_soa_cache_.reset(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE, class_));
+ // Set the message cache size to 1, make it easy for unittest.
+ message_cache_.reset(new DerivedMessageCache(rrset_cache_, 1, class_,
+ negative_soa_cache_));
}
protected:
boost::shared_ptr<DerivedMessageCache> message_cache_;
boost::shared_ptr<DerivedRRsetCache> rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
Message message_parse;
Message message_render;
};
+void
+updateMessageCache(const char* message_file,
+ boost::shared_ptr<DerivedMessageCache> cache)
+{
+ Message msg(Message::PARSE);
+ messageFromFile(msg, message_file);
+ cache->update(msg);
+}
+
TEST_F(MessageCacheTest, testLookup) {
messageFromFile(message_parse, "message_fromWire1");
EXPECT_TRUE(message_cache_->update(message_parse));
+
Name qname("test.example.com.");
EXPECT_TRUE(message_cache_->lookup(qname, RRType::A(), message_render));
EXPECT_EQ(message_cache_->messages_count(), 1);
@@ -96,10 +110,19 @@ TEST_F(MessageCacheTest, testLookup) {
Name qname1("test.example.net.");
EXPECT_TRUE(message_cache_->lookup(qname1, RRType::A(), message_render));
- // Test looking up message which has expired rrsets.
- // Remove one
+ // Test looking up message which has expired rrset or some rrset
+ // has been removed from the rrset cache.
rrset_cache_->removeRRsetEntry(qname1, RRType::A());
EXPECT_FALSE(message_cache_->lookup(qname1, RRType::A(), message_render));
+
+ // Update one message entry which has expired to message cache.
+ updateMessageCache("message_fromWire9", message_cache_);
+ EXPECT_EQ(message_cache_->messages_count(), 3);
+ // The message entry has been added, but can't be looked up, since
+ // it has expired and is removed automatically when being looked up.
+ Name qname_org("test.example.org.");
+ EXPECT_FALSE(message_cache_->lookup(qname_org, RRType::A(), message_render));
+ EXPECT_EQ(message_cache_->messages_count(), 2);
}
TEST_F(MessageCacheTest, testUpdate) {
@@ -115,7 +138,24 @@ TEST_F(MessageCacheTest, testUpdate) {
EXPECT_TRUE(message_cache_->update(new_msg));
Message new_msg_render(Message::RENDER);
EXPECT_TRUE(message_cache_->lookup(qname, RRType::SOA(), new_msg_render));
- EXPECT_TRUE(new_msg_render.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(new_msg_render.getHeaderFlag(Message::HEADERFLAG_AA));
+}
+
+TEST_F(MessageCacheTest, testCacheLruBehavior) {
+ // qname = "test.example.com.", qtype = A
+ updateMessageCache("message_fromWire1", message_cache_);
+ // qname = "test.example.net.", qtype = A
+ updateMessageCache("message_fromWire2", message_cache_);
+ // qname = "example.com.", qtype = SOA
+ updateMessageCache("message_fromWire4", message_cache_);
+
+ Name qname_net("test.example.net.");
+ EXPECT_TRUE(message_cache_->lookup(qname_net, RRType::A(), message_render));
+
+ // qname = "a.example.com.", qtype = A
+ updateMessageCache("message_fromWire5", message_cache_);
+ Name qname_com("test.example.com.");
+ EXPECT_FALSE(message_cache_->lookup(qname_com, RRType::A(), message_render));
}
} // namespace
diff --git a/src/lib/cache/tests/message_entry_unittest.cc b/src/lib/cache/tests/message_entry_unittest.cc
index a96c441..2ca33ec 100644
--- a/src/lib/cache/tests/message_entry_unittest.cc
+++ b/src/lib/cache/tests/message_entry_unittest.cc
@@ -1,5 +1,3 @@
-// Copyright (C) 2010 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.
@@ -38,14 +36,15 @@ namespace {
class DerivedMessageEntry: public MessageEntry {
public:
DerivedMessageEntry(const isc::dns::Message& message,
- boost::shared_ptr<RRsetCache> rrset_cache_):
- MessageEntry(message, rrset_cache_)
+ const RRsetCachePtr& rrset_cache_,
+ const RRsetCachePtr& negative_soa_cache_):
+ MessageEntry(message, rrset_cache_, negative_soa_cache_)
{}
- /// \brief Wrap the protected function so that it can be tested.
+ /// \brief Wrap the protected function so that it can be tested.
void parseSectionForTest(const Message& msg,
const Message::Section& section,
- uint32_t& smaller_ttl,
+ uint32_t& smaller_ttl,
uint16_t& rrset_count)
{
parseSection(msg, section, smaller_ttl, rrset_count);
@@ -75,18 +74,20 @@ public:
message_render(Message::RENDER)
{
rrset_cache_.reset(new RRsetCache(RRSET_CACHE_DEFAULT_SIZE, class_));
+ negative_soa_cache_.reset(new RRsetCache(NEGATIVE_RRSET_CACHE_DEFAULT_SIZE, class_));
}
protected:
uint16_t class_;
RRsetCachePtr rrset_cache_;
+ RRsetCachePtr negative_soa_cache_;
Message message_parse;
Message message_render;
};
TEST_F(MessageEntryTest, testParseRRset) {
messageFromFile(message_parse, "message_fromWire3");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
uint32_t ttl = MAX_UINT32;
uint16_t rrset_count = 0;
message_entry.parseSectionForTest(message_parse, Message::SECTION_ANSWER, ttl, rrset_count);
@@ -106,7 +107,7 @@ TEST_F(MessageEntryTest, testParseRRset) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_AA) {
messageFromFile(message_parse, "message_fromWire3");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
@@ -129,7 +130,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_AA) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_NONAA) {
messageFromFile(message_parse, "message_fromWire4");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
*rrset_iter,
@@ -151,7 +152,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_NONAA) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME) {
messageFromFile(message_parse, "message_fromWire5");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
*rrset_iter,
@@ -167,7 +168,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME_and_DNAME) {
messageFromFile(message_parse, "message_fromWire7");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
*rrset_iter,
@@ -186,7 +187,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_CNAME_and_DNAME) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME_and_CNAME) {
messageFromFile(message_parse, "message_fromWire8");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
*rrset_iter,
@@ -214,7 +215,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME_and_CNAME) {
TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) {
messageFromFile(message_parse, "message_fromWire6");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
RRsetIterator rrset_iter = message_parse.beginSection(Message::SECTION_ANSWER);
RRsetTrustLevel level = message_entry.getRRsetTrustLevelForTest(message_parse,
*rrset_iter,
@@ -239,7 +240,7 @@ TEST_F(MessageEntryTest, testGetRRsetTrustLevel_DNAME) {
// is right
TEST_F(MessageEntryTest, testInitMessageEntry) {
messageFromFile(message_parse, "message_fromWire3");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
time_t expire_time = message_entry.getExpireTime();
// 1 second should be enough to do the compare
EXPECT_TRUE((time(NULL) + 10801) > expire_time);
@@ -247,7 +248,7 @@ TEST_F(MessageEntryTest, testInitMessageEntry) {
TEST_F(MessageEntryTest, testGetRRsetEntries) {
messageFromFile(message_parse, "message_fromWire3");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
vector<RRsetEntryPtr> vec;
// the time is bigger than the smallest expire time of
@@ -258,15 +259,14 @@ TEST_F(MessageEntryTest, testGetRRsetEntries) {
TEST_F(MessageEntryTest, testGenMessage) {
messageFromFile(message_parse, "message_fromWire3");
- DerivedMessageEntry message_entry(message_parse, rrset_cache_);
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
time_t expire_time = message_entry.getExpireTime();
Message msg(Message::RENDER);
EXPECT_FALSE(message_entry.genMessage(expire_time + 2, msg));
message_entry.genMessage(time(NULL), msg);
// Check whether the generated message is same with cached one.
-
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_TC));
EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_ANSWER));
EXPECT_EQ(1, sectionRRsetCount(msg, Message::SECTION_AUTHORITY));
@@ -278,4 +278,32 @@ TEST_F(MessageEntryTest, testGenMessage) {
EXPECT_EQ(7, msg.getRRCount(Message::SECTION_ADDITIONAL));
}
+TEST_F(MessageEntryTest, testMaxTTL) {
+ messageFromFile(message_parse, "message_large_ttl.wire");
+
+ // The ttl of rrset from Answer and Authority sections are both 604801 seconds
+ RRsetIterator iter = message_parse.beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(604801, (*iter)->getTTL().getValue());
+ iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ(604801, (*iter)->getTTL().getValue());
+
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+
+ // The ttl is limited to 604800 seconds (7days)
+ EXPECT_EQ(time(NULL) + 604800, message_entry.getExpireTime());
+}
+
+TEST_F(MessageEntryTest, testMaxNegativeTTL) {
+ messageFromFile(message_parse, "message_nxdomain_large_ttl.wire");
+
+ // The ttl of rrset Authority sections are 10801 seconds
+ RRsetIterator iter = message_parse.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ(10801, (*iter)->getTTL().getValue());
+
+ DerivedMessageEntry message_entry(message_parse, rrset_cache_, negative_soa_cache_);
+
+ // The ttl is limited to 10800 seconds (3 hours)
+ EXPECT_EQ(time(NULL) + 10800, message_entry.getExpireTime());
+}
+
} // namespace
diff --git a/src/lib/cache/tests/negative_cache_unittest.cc b/src/lib/cache/tests/negative_cache_unittest.cc
new file mode 100644
index 0000000..56d777d
--- /dev/null
+++ b/src/lib/cache/tests/negative_cache_unittest.cc
@@ -0,0 +1,242 @@
+// 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.
+
+// $Id$
+#include <config.h>
+#include <string>
+#include <gtest/gtest.h>
+#include <dns/rrset.h>
+#include <dns/rcode.h>
+#include "resolver_cache.h"
+#include "cache_test_messagefromfile.h"
+
+using namespace isc::cache;
+using namespace isc::dns;
+using namespace std;
+
+namespace {
+
+class NegativeCacheTest: public testing::Test{
+public:
+ NegativeCacheTest() {
+ vector<CacheSizeInfo> vec;
+ CacheSizeInfo class_in(RRClass::IN(), 100, 200);
+ vec.push_back(class_in);
+ cache = new ResolverCache(vec);
+ }
+
+ ~NegativeCacheTest() {
+ delete cache;
+ }
+
+ ResolverCache *cache;
+};
+
+TEST_F(NegativeCacheTest, testNXDOMAIN){
+ // NXDOMAIN response for nonexist.example.com
+ Message msg_nxdomain(Message::PARSE);
+ messageFromFile(msg_nxdomain, "message_nxdomain_with_soa.wire");
+ cache->update(msg_nxdomain);
+
+ msg_nxdomain.makeResponse();
+
+ Name non_exist_qname("nonexist.example.com.");
+ EXPECT_TRUE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain));
+
+ RRsetIterator iter = msg_nxdomain.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record
+ const RRTTL& nxdomain_ttl1 = rrset_ptr->getTTL();
+ EXPECT_EQ(nxdomain_ttl1.getValue(), 86400);
+
+ // SOA response for example.com
+ Message msg_example_com_soa(Message::PARSE);
+ messageFromFile(msg_example_com_soa, "message_example_com_soa.wire");
+ cache->update(msg_example_com_soa);
+
+ msg_example_com_soa.makeResponse();
+ Name soa_qname("example.com.");
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+
+ iter = msg_example_com_soa.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record in answer section
+ const RRTTL& soa_ttl = rrset_ptr->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 172800);
+
+ sleep(1);
+
+ // Query nonexist.example.com again
+ Message msg_nxdomain2(Message::PARSE);
+ messageFromFile(msg_nxdomain2, "message_nxdomain_with_soa.wire");
+ msg_nxdomain2.makeResponse();
+
+ EXPECT_TRUE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain2));
+ iter = msg_nxdomain2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of negative response SOA record
+ const RRTTL& nxdomain_ttl2 = rrset_ptr->getTTL();
+ EXPECT_TRUE(86398 <= nxdomain_ttl2.getValue() && nxdomain_ttl2.getValue() <= 86399);
+ // No RRset in ANSWER section
+ EXPECT_TRUE(msg_nxdomain2.getRRCount(Message::SECTION_ANSWER) == 0);
+ // Check that only one SOA record exist in AUTHORITY section
+ EXPECT_TRUE(msg_nxdomain2.getRRCount(Message::SECTION_AUTHORITY) == 1);
+ iter = msg_nxdomain2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+ EXPECT_TRUE(rrset_ptr->getType() == RRType::SOA());
+
+ // Check the normal SOA cache again
+ Message msg_example_com_soa2(Message::PARSE);
+ messageFromFile(msg_example_com_soa2, "message_example_com_soa.wire");
+ msg_example_com_soa2.makeResponse();
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa2));
+
+ iter = msg_example_com_soa2.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+ const RRTTL& soa_ttl2 = rrset_ptr->getTTL();
+ // The TTL should equal to the TTL of SOA record in answer section
+ EXPECT_TRUE(172798 <= soa_ttl2.getValue() && soa_ttl2.getValue() <= 172799);
+}
+
+TEST_F(NegativeCacheTest, testNXDOMAINWithoutSOA){
+ // NXDOMAIN response for nonexist.example.com
+ Message msg_nxdomain(Message::PARSE);
+ messageFromFile(msg_nxdomain, "message_nxdomain_no_soa.wire");
+ cache->update(msg_nxdomain);
+
+ msg_nxdomain.makeResponse();
+
+ Name non_exist_qname("nonexist.example.com.");
+ // The message should not be cached
+ EXPECT_FALSE(cache->lookup(non_exist_qname, RRType::A(), RRClass::IN(), msg_nxdomain));
+}
+
+TEST_F(NegativeCacheTest, testNXDOMAINCname){
+ // a.example.org points to b.example.org
+ // b.example.org points to c.example.org
+ // c.example.org does not exist
+ Message msg_nxdomain_cname(Message::PARSE);
+ messageFromFile(msg_nxdomain_cname, "message_nxdomain_cname.wire");
+ cache->update(msg_nxdomain_cname);
+
+ msg_nxdomain_cname.makeResponse();
+
+ Name a_example_org("a.example.org.");
+ // The message should be cached
+ EXPECT_TRUE(cache->lookup(a_example_org, RRType::A(), RRClass::IN(), msg_nxdomain_cname));
+
+ EXPECT_EQ(msg_nxdomain_cname.getRcode().getCode(), Rcode::NXDOMAIN().getCode());
+
+ // It should include 2 CNAME records in Answer section
+ EXPECT_TRUE(msg_nxdomain_cname.getRRCount(Message::SECTION_ANSWER) == 2);
+ RRsetIterator iter = msg_nxdomain_cname.beginSection(Message::SECTION_ANSWER);
+ EXPECT_TRUE((*iter)->getType() == RRType::CNAME());
+ ++iter;
+ EXPECT_TRUE((*iter)->getType() == RRType::CNAME());
+
+ // It should include 1 SOA record in Authority section
+ EXPECT_TRUE(msg_nxdomain_cname.getRRCount(Message::SECTION_AUTHORITY) == 1);
+ iter = msg_nxdomain_cname.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_TRUE((*iter)->getType() == RRType::SOA());
+
+ const RRTTL& soa_ttl = (*iter)->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 600);
+}
+
+TEST_F(NegativeCacheTest, testNoerrorNodata){
+ // NODATA/NOERROR response for MX type query of example.com
+ Message msg_nodata(Message::PARSE);
+ messageFromFile(msg_nodata, "message_nodata_with_soa.wire");
+ cache->update(msg_nodata);
+
+ msg_nodata.makeResponse();
+
+ Name example_dot_com("example.com.");
+ EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata));
+
+ RRsetIterator iter = msg_nodata.beginSection(Message::SECTION_AUTHORITY);
+ RRsetPtr rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record
+ const RRTTL& nodata_ttl1 = rrset_ptr->getTTL();
+ EXPECT_EQ(nodata_ttl1.getValue(), 86400);
+
+
+ // Normal SOA response for example.com
+ Message msg_example_com_soa(Message::PARSE);
+ messageFromFile(msg_example_com_soa, "message_example_com_soa.wire");
+ cache->update(msg_example_com_soa);
+
+ msg_example_com_soa.makeResponse();
+ Name soa_qname("example.com.");
+ EXPECT_TRUE(cache->lookup(soa_qname, RRType::SOA(), RRClass::IN(), msg_example_com_soa));
+
+ iter = msg_example_com_soa.beginSection(Message::SECTION_ANSWER);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of SOA record in answer section
+ const RRTTL& soa_ttl = rrset_ptr->getTTL();
+ EXPECT_EQ(soa_ttl.getValue(), 172800);
+
+ // Query MX record of example.com again
+ Message msg_nodata2(Message::PARSE);
+ messageFromFile(msg_nodata2, "message_nodata_with_soa.wire");
+ msg_nodata2.makeResponse();
+
+ sleep(1);
+
+ EXPECT_TRUE(cache->lookup(example_dot_com, RRType::MX(), RRClass::IN(), msg_nodata2));
+
+ // No answer
+ EXPECT_EQ(msg_nodata2.getRRCount(Message::SECTION_ANSWER), 0);
+ // One SOA record in authority section
+ EXPECT_EQ(msg_nodata2.getRRCount(Message::SECTION_AUTHORITY), 1);
+
+ iter = msg_nodata2.beginSection(Message::SECTION_AUTHORITY);
+ rrset_ptr = *iter;
+
+ // The TTL should equal to the TTL of negative response SOA record and counted down
+ const RRTTL& nodata_ttl2 = rrset_ptr->getTTL();
+ EXPECT_TRUE(86398 <= nodata_ttl2.getValue() && nodata_ttl2.getValue() <= 86399);
+}
+
+TEST_F(NegativeCacheTest, testReferralResponse){
+ // CNAME exist, but it points to out of zone data, so the server give some reference data
+ Message msg_cname_referral(Message::PARSE);
+ messageFromFile(msg_cname_referral, "message_cname_referral.wire");
+ cache->update(msg_cname_referral);
+
+ msg_cname_referral.makeResponse();
+
+ Name x_example_org("x.example.org.");
+ EXPECT_TRUE(cache->lookup(x_example_org, RRType::A(), RRClass::IN(), msg_cname_referral));
+
+ // The Rcode should be NOERROR
+ EXPECT_EQ(msg_cname_referral.getRcode().getCode(), Rcode::NOERROR().getCode());
+
+ // One CNAME record in Answer section
+ EXPECT_EQ(msg_cname_referral.getRRCount(Message::SECTION_ANSWER), 1);
+ RRsetIterator iter = msg_cname_referral.beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ((*iter)->getType(), RRType::CNAME());
+
+ // 13 NS records in Authority section
+ EXPECT_EQ(msg_cname_referral.getRRCount(Message::SECTION_AUTHORITY), 13);
+ iter = msg_cname_referral.beginSection(Message::SECTION_AUTHORITY);
+ EXPECT_EQ((*iter)->getType(), RRType::NS());
+}
+
+}
diff --git a/src/lib/cache/tests/resolver_cache_unittest.cc b/src/lib/cache/tests/resolver_cache_unittest.cc
index 20dcec9..7b686f5 100644
--- a/src/lib/cache/tests/resolver_cache_unittest.cc
+++ b/src/lib/cache/tests/resolver_cache_unittest.cc
@@ -53,7 +53,7 @@ TEST_F(ResolverCacheTest, testUpdateMessage) {
msg.makeResponse();
EXPECT_TRUE(cache->lookup(qname, RRType::SOA(), RRClass::IN(), msg));
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
+ EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
// Test whether the old message can be updated
Message new_msg(Message::PARSE);
diff --git a/src/lib/cache/tests/rrset_cache_unittest.cc b/src/lib/cache/tests/rrset_cache_unittest.cc
index afb7eaa..b61f5c4 100644
--- a/src/lib/cache/tests/rrset_cache_unittest.cc
+++ b/src/lib/cache/tests/rrset_cache_unittest.cc
@@ -34,50 +34,94 @@ namespace {
class RRsetCacheTest : public testing::Test {
protected:
RRsetCacheTest():
- cache(RRSET_CACHE_DEFAULT_SIZE, RRClass::IN().getCode()),
- name("example.com"),
- rrset1(name, RRClass::IN(), RRType::A(), RRTTL(20)),
- rrset2(name, RRClass::IN(), RRType::A(), RRTTL(10)),
- rrset_entry1(rrset1, RRSET_TRUST_ADDITIONAL_AA),
- rrset_entry2(rrset2, RRSET_TRUST_PRIM_ZONE_NONGLUE)
+ cache_(1, RRClass::IN().getCode()),
+ name_("example.com"),
+ rrset1_(name_, RRClass::IN(), RRType::A(), RRTTL(20)),
+ rrset2_(name_, RRClass::IN(), RRType::A(), RRTTL(10)),
+ rrset_entry1_(rrset1_, RRSET_TRUST_ADDITIONAL_AA),
+ rrset_entry2_(rrset2_, RRSET_TRUST_PRIM_ZONE_NONGLUE)
{
}
- RRsetCache cache;
- Name name;
- RRset rrset1;
- RRset rrset2;
- RRsetEntry rrset_entry1;
- RRsetEntry rrset_entry2;
+ RRsetCache cache_;
+ Name name_;
+ RRset rrset1_;
+ RRset rrset2_;
+ RRsetEntry rrset_entry1_;
+ RRsetEntry rrset_entry2_;
};
+void
+updateRRsetCache(RRsetCache& cache, Name& rrset_name,
+ uint32_t ttl = 20,
+ RRsetTrustLevel level = RRSET_TRUST_ADDITIONAL_AA)
+{
+ RRset rrset(rrset_name, RRClass::IN(), RRType::A(), RRTTL(ttl));
+ cache.update(rrset, level);
+}
+
TEST_F(RRsetCacheTest, lookup) {
const RRType& type = RRType::A();
- EXPECT_TRUE(cache.lookup(name, type) == NULL);
-
- cache.update(rrset1, rrset_entry1.getTrustLevel());
- RRsetEntryPtr rrset_entry_ptr = cache.lookup(name, type);
- EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1.getTrustLevel());
- EXPECT_EQ(rrset_entry_ptr->getRRset()->getName(), rrset_entry1.getRRset()->getName());
- EXPECT_EQ(rrset_entry_ptr->getRRset()->getType(), rrset_entry1.getRRset()->getType());
- EXPECT_EQ(rrset_entry_ptr->getRRset()->getClass(), rrset_entry1.getRRset()->getClass());
+ EXPECT_TRUE(cache_.lookup(name_, type) == NULL);
+
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ RRsetEntryPtr rrset_entry_ptr = cache_.lookup(name_, type);
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1_.getTrustLevel());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getName(), rrset_entry1_.getRRset()->getName());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getType(), rrset_entry1_.getRRset()->getType());
+ EXPECT_EQ(rrset_entry_ptr->getRRset()->getClass(), rrset_entry1_.getRRset()->getClass());
+
+ // Check whether the expired rrset entry will be removed automatically
+ // when looking up.
+ Name name_test("test.example.com.");
+ updateRRsetCache(cache_, name_test, 0); // Add a rrset with TTL 0 to cache.
+ EXPECT_FALSE(cache_.lookup(name_test, RRType::A()));
}
TEST_F(RRsetCacheTest, update) {
const RRType& type = RRType::A();
- cache.update(rrset1, rrset_entry1.getTrustLevel());
- RRsetEntryPtr rrset_entry_ptr = cache.lookup(name, type);
- EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1.getTrustLevel());
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
+ RRsetEntryPtr rrset_entry_ptr = cache_.lookup(name_, type);
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry1_.getTrustLevel());
- cache.update(rrset2, rrset_entry2.getTrustLevel());
- rrset_entry_ptr = cache.lookup(name, type);
+ cache_.update(rrset2_, rrset_entry2_.getTrustLevel());
+ rrset_entry_ptr = cache_.lookup(name_, type);
// The trust level should be updated
- EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2.getTrustLevel());
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2_.getTrustLevel());
- cache.update(rrset1, rrset_entry1.getTrustLevel());
+ cache_.update(rrset1_, rrset_entry1_.getTrustLevel());
// The trust level should not be updated
- EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2.getTrustLevel());
+ EXPECT_EQ(rrset_entry_ptr->getTrustLevel(), rrset_entry2_.getTrustLevel());
+}
+
+// Test whether the lru list in rrset cache works as expected.
+TEST_F(RRsetCacheTest, cacheLruBehavior) {
+ Name name1("1.example.com.");
+ Name name2("2.example.com.");
+ Name name3("3.example.com.");
+ Name name4("4.example.com.");
+
+ updateRRsetCache(cache_, name1);
+ updateRRsetCache(cache_, name2);
+ updateRRsetCache(cache_, name3);
+
+ EXPECT_TRUE(cache_.lookup(name1, RRType::A()));
+
+ // Now update the fourth rrset, rrset with name "2.example.com."
+ // should has been removed from cache.
+ updateRRsetCache(cache_, name4);
+ EXPECT_FALSE(cache_.lookup(name2, RRType::A()));
+
+ // Test Update rrset with higher trust level
+ updateRRsetCache(cache_, name1, RRSET_TRUST_PRIM_GLUE);
+ // Test update rrset with lower trust level.
+ updateRRsetCache(cache_, name3, RRSET_TRUST_ADDITIONAL_NONAA);
+
+ // When add rrset with name2, rrset with name4
+ // has been removed from the cache.
+ updateRRsetCache(cache_, name2);
+ EXPECT_FALSE(cache_.lookup(name4, RRType::A()));
}
}
diff --git a/src/lib/cache/tests/testdata/message_cname_referral.wire b/src/lib/cache/tests/testdata/message_cname_referral.wire
new file mode 100644
index 0000000..7c6b285
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_cname_referral.wire
@@ -0,0 +1,56 @@
+#
+# Request A record for x.example.org, the CNAME record exist for x.example.org
+# it poinst to x.example.net, but the server has no idea whether x.example.net exist
+# so it give some NS records for reference
+#
+# Transaction ID: 0xaf71
+# Flags: 0x8480 (Standard query response, No error)
+af71 8480
+# Questions: 1
+# Answer RRs: 1
+# Authority RRs: 13
+# Additional RRs: 0
+00 01 00 01 00 0d 00 00
+##
+## query
+##
+# x.example.org: type A, class IN
+##
+## Answer
+##
+# x.example.org: type CNAME, class IN, cname x.example.net
+# TTL: 360s
+01 78 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+c0 0c 00 05 00 01 00 00 0e 10 00 0f 01 78 07 65 78
+61 6d 70 6c 65 03 6e 65 74 00
+##
+## Authority
+##
+# TTL:518400
+# <Root>: type NS, class IN, ns G.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns E.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns J.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns L.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns H.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns I.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns K.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns M.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns F.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns B.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns C.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns D.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns A.ROOT-SERVERS.net
+00 00 02 00 01 00
+07 e9 00 00 11 01 47 0c 52 4f 4f 54 2d 53 45 52
+56 45 52 53 c0 35 00 00 02 00 01 00 07 e9 00 00
+04 01 45 c0 47 00 00 02 00 01 00 07 e9 00 00 04
+01 4a c0 47 00 00 02 00 01 00 07 e9 00 00 04 01
+4c c0 47 00 00 02 00 01 00 07 e9 00 00 04 01 48
+c0 47 00 00 02 00 01 00 07 e9 00 00 04 01 49 c0
+47 00 00 02 00 01 00 07 e9 00 00 04 01 4b c0 47
+00 00 02 00 01 00 07 e9 00 00 04 01 4d c0 47 00
+00 02 00 01 00 07 e9 00 00 04 01 46 c0 47 00 00
+02 00 01 00 07 e9 00 00 04 01 42 c0 47 00 00 02
+00 01 00 07 e9 00 00 04 01 43 c0 47 00 00 02 00
+01 00 07 e9 00 00 04 01 44 c0 47 00 00 02 00 01
+00 07 e9 00 00 04 01 41 c0 47
diff --git a/src/lib/cache/tests/testdata/message_example_com_soa.wire b/src/lib/cache/tests/testdata/message_example_com_soa.wire
new file mode 100644
index 0000000..6d70ed7
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_example_com_soa.wire
@@ -0,0 +1,57 @@
+#
+# SOA request response for example.com
+#
+# Transaction ID: 0x7f36
+# Flags: 0x8400 (Standard query response, No error)
+7f 36 84 00
+# Questions: 1
+00 01
+# Answer RRs: 1
+00 01
+# Authority RRs: 2
+00 02
+# Additional RRs: 0
+00 00
+##
+## Query
+##
+# Name: example.com
+07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type: SOA (Start of zone of authority)
+00 06
+# Class: IN (0x0001)
+00 01
+##
+## Answers
+##
+# Name: example.com
+c0 0c
+# Type: SOA (Start of zone of authority)
+00 06
+# Class: IN (0x0001)
+00 01
+# Time to live: 2 days (172800s)
+00 02 a3 00
+# Data length: 49
+00 31
+# Primary name server: dns1.icann.org
+04 64 6e 73 31 05 69 63 61 6e 6e 03 6f 72 67 00
+# Responsible authority's mailbox: hostmaster.icann.org
+0a 68 6f 73 74 6d 61 73 74 65 72 c0 2e
+# Serial number: 2010072301
+77 cf 44 ed
+# Refresh interval: 2 hours
+00 00 1c 20
+# Retry interval: 1 hour
+00 00 0e 10
+# Expiration limit: 14 days
+00 12 75 00
+# Minimum TTL: 1 day
+00 01 51 80
+##
+## Authoritative nameservers
+##
+# example.com: type NS, class IN, ns a.iana-servers.net
+c0 0c 00 02 00 01 00 02 a3 00 00 14 01 61 0c 69 61 6e 61 2d 73 65 72 76 65 72 73 03 6e 65 74 00
+# example.com: type NS, class IN, ns b.iana-servers.net
+c0 0c 00 02 00 01 00 02 a3 00 00 04 01 62 c0 68
diff --git a/src/lib/cache/tests/testdata/message_fromWire9 b/src/lib/cache/tests/testdata/message_fromWire9
new file mode 100644
index 0000000..e2dbd06
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_fromWire9
@@ -0,0 +1,25 @@
+#
+# The TTL for a record in answer section is 0, so it
+# will expire immediately after being cached.
+#
+# A simple DNS response message
+# ID = 0x1035
+# QR=1 (response), Opcode=0, AA=1, RD=1 (other fields are 0)
+# QDCOUNT=1, ANCOUNT=2, other COUNTS=0
+# Question: test.example.org. IN A
+# Answer:
+# test.example.org. 0000 IN A 192.0.2.1
+# test.example.org. 7200 IN A 192.0.2.2
+#
+1035 8500
+0001 0002 0000 0000
+#(4) t e s t (7) e x a m p l e (3) o r g .
+ 04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6f 72 67 00
+0001 0001
+# same name, fully compressed
+c0 0c
+# TTL=3600, A, IN, RDLENGTH=4, RDATA
+0001 0001 00000000 0004 c0 00 02 01
+# mostly same, with the slight difference in RDATA and TTL
+c0 0c
+0001 0001 00001c20 0004 c0 00 02 02
diff --git a/src/lib/cache/tests/testdata/message_large_ttl.wire b/src/lib/cache/tests/testdata/message_large_ttl.wire
new file mode 100644
index 0000000..6e152ef
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_large_ttl.wire
@@ -0,0 +1,31 @@
+#
+# A response that the TTL is quite large(> 7days)
+#
+##
+## header
+##
+# Transaction ID: 0x0d1f
+# Flags: 0x8580 (Standard query response, No error)
+0d1f 8580
+# Questions: 1
+# Answer RRs: 1
+# Authority RRs: 3
+# Additional RRs: 3
+00 01 00 01 00 01 00 00
+##
+## Query
+##
+# test.example.org: type A, class IN
+04 74 65 73 74 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Answer
+##
+# test.example.org: type A, class IN, addr 127.0.0.1
+# TTL: 7 days, 1 second (604801 seconds)
+c0 0c 00 01 00 01 00 09 3a 81 00 04 7f 00 00 01
+##
+## Authority
+##
+# example.org: type NS, class IN, ns ns1.example.org
+# TTL: 7 days, 1 second (604801 seconds)
+c0 11 00 02 00 01 00 09 3a 81 00 06 03 6e 73 31 c0 11
diff --git a/src/lib/cache/tests/testdata/message_nodata_with_soa.wire b/src/lib/cache/tests/testdata/message_nodata_with_soa.wire
new file mode 100644
index 0000000..67a4adc
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nodata_with_soa.wire
@@ -0,0 +1,32 @@
+#
+# NOERROR/NODATA response with SOA record
+#
+##
+## header
+##
+#Transaction ID: 0x0284
+#Flags: 0x8500 (Standard query response, No error)
+0284 8500
+#Question:1
+00 01
+#Answer RRs:0
+00 00
+#Authority RRs:1
+00 01
+#Additional RRs:0
+00 00
+##
+## Queries
+##
+# example.com: type MX, class IN
+07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 0f 00 01
+##
+## Authoritative nameservers
+##
+# example.com: type SOA, class IN, mname dns1.icann.org
+# TTL:86400
+c0 0c 00
+06 00 01 00 01 51 80 00 31 04 64 6e 73 31 05 69
+63 61 6e 6e 03 6f 72 67 00 0a 68 6f 73 74 6d 61
+73 74 65 72 c0 2e 77 cf 44 ed 00 00 1c 20 00 00
+0e 10 00 12 75 00 00 01 51 80
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_cname.wire b/src/lib/cache/tests/testdata/message_nxdomain_cname.wire
new file mode 100644
index 0000000..1ae3d76
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_cname.wire
@@ -0,0 +1,36 @@
+#
+# NXDOMAIN response
+# The cname type of a.example.org exist, it points to b.example.org
+# b.example.org points to c.example.org
+# but c.example.org does not exist
+#
+##
+## header
+##
+# Transaction ID: 0xc2aa
+# Flags: 0x8583 (Standard query response, No such name)
+c2aa 8583
+# Questions: 1
+# Answer RRs: 2
+# Authority RRs: 1
+# dditional RRs: 0
+00 01 00 02 00 01 00 00
+##
+## Queries
+##
+# a.example.org: type A, class IN
+01 61 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Answers
+##
+# a.example.org: type CNAME, class IN, cname b.example.org
+c0 0c 00 05 00 01 00 00 0e 10 00 04 01 62 c0 0e
+# b.example.org: type CNAME, class IN, cname c.example.org
+c0 2b 00 05 00 01 00 00 0e 10 00 04 01 63 c0 0e
+##
+## Authority
+##
+# example.org: type SOA, class IN, mname ns1.example.org
+c0 0e 00 06 00 01 00 00 02 58 00 22 03 6e 73 31 c0
+0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
+10 00 00 07 08 00 24 ea 00 00 00 02 58
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
new file mode 100644
index 0000000..142d8cf
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_large_ttl.wire
@@ -0,0 +1,25 @@
+#
+# Negative response (NXDOMAIN) with large TTL (3hours + 1second)
+#
+##
+## Header
+##
+# Transaction ID: 0xb1fe
+# Flags: 0x8583 (Standard query response, No such name)
+b1fe 8583
+# Questions: 1
+# Authority RRs: 1
+00 01 00 00 00 01 00 00
+##
+## Query
+##
+# c.example.org: type A, class IN
+01 63 07 65 78 61 6d 70 6c 65 03 6f 72 67 00 00 01 00 01
+##
+## Authority
+##
+# example.org: type SOA, class IN, mname ns1.example.org
+# TTL: 3 Hourse, 1 second (10801seconds)
+c0 0e 00 06 00 01 00 00 2a 31 00 22 03 6e 73 31 c0
+0e 05 61 64 6d 69 6e c0 0e 00 00 04 d2 00 00 0e
+10 00 00 07 08 00 24 ea 00 00 00 2a 31
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire b/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
new file mode 100644
index 0000000..b138cc2
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_no_soa.wire
@@ -0,0 +1,26 @@
+#
+# NXDOMAIN response with SOA record
+#
+##
+## Header
+##
+# ID = 0x3da0
+# QR = 1 (response), Opcode = 0, AA = 1, RCODE=3 (NXDOMAIN)
+3da0 8403
+# Question : 1
+00 01
+# Answer : 0
+00 00
+# Authority : 0
+00 00
+# Additional : 0
+00 00
+##
+## Query
+##
+#(4) n o n e x i s t (7) e x a m p l e (3) c o m (0)
+ 08 6e 6f 6e 65 78 69 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type:A
+00 01
+# class: IN
+00 01
diff --git a/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire b/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
new file mode 100644
index 0000000..04fd66f
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_nxdomain_with_soa.wire
@@ -0,0 +1,55 @@
+#
+# NXDOMAIN response with SOA record
+#
+##
+## Header
+##
+# ID = 0x3da0
+# QR = 1 (response), Opcode = 0, AA = 1, RCODE=3 (NXDOMAIN)
+3da0 8403
+# Question : 1
+00 01
+# Answer : 0
+00 00
+# Authority : 1
+00 01
+# Additional : 0
+00 00
+##
+## Query
+##
+#(4) n o n e x i s t (7) e x a m p l e (3) c o m (0)
+ 08 6e 6f 6e 65 78 69 73 74 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00
+# Type:A
+00 01
+# class: IN
+00 01
+##
+## Authority
+##
+# name: example.com
+c0 15
+# Type:SOA
+00 06
+# Class: IN
+00 01
+# TTL: 86400
+00 01 51 80
+# Data Length: 49
+00 31
+# Name Server:
+#(4) d n s 1 (5) i c a n n (3) o r g (0)
+ 04 64 6e 73 31 05 69 63 61 6e 6e 03 6f 72 67 00
+# MX:
+# (10) h o s t m a s t e r .icann.org.
+ 0a 68 6f 73 74 6d 61 73 74 65 72 c0 37
+# Serial Number:2010072301
+77 cf 44 ed
+# Refresh Interval:2 hours
+00 00 1c 20
+# Retry Interval: 1 hour
+00 00 0e 10
+# Expiration: 14 days
+00 12 75 00
+# Minimum TTL 1 day
+00 01 51 80
diff --git a/src/lib/cache/tests/testdata/message_referral.wire b/src/lib/cache/tests/testdata/message_referral.wire
new file mode 100644
index 0000000..bb897ca
--- /dev/null
+++ b/src/lib/cache/tests/testdata/message_referral.wire
@@ -0,0 +1,36 @@
+#
+# Query x.example.net to nameservr of example.org
+# It will just give some referral info
+#
+#
+# Transaction ID: 0x8b61
+# Flags: 0x8080 (Standard query response, No error)
+8b61 8080
+# Questions: 1
+# Authority RRs: 13
+00 01 00 00 00 0d 00 00
+##
+## Query
+##
+# x.example.net: type A, class IN
+01 78 07 65 78 61 6d 70 6c 65 03 6e 65 74 00 00 01 00 01
+##
+## Authority
+##
+# <Root>: type NS, class IN, ns B.ROOT-SERVERS.net
+# <Root>: type NS, class IN, ns M.ROOT-SERVERS.net
+# ...
+# <Root>: type NS, class IN, ns H.ROOT-SERVERS.net
+00 00 02 00 01 00 07 e9 00 00 11 01 42 0c 52 4f 4f
+54 2d 53 45 52 56 45 52 53 c0 16 00 00 02 00 01
+00 07 e9 00 00 04 01 4d c0 2c 00 00 02 00 01 00
+07 e9 00 00 04 01 44 c0 2c 00 00 02 00 01 00 07
+e9 00 00 04 01 4c c0 2c 00 00 02 00 01 00 07 e9
+00 00 04 01 4b c0 2c 00 00 02 00 01 00 07 e9 00
+00 04 01 43 c0 2c 00 00 02 00 01 00 07 e9 00 00
+04 01 41 c0 2c 00 00 02 00 01 00 07 e9 00 00 04
+01 49 c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01
+45 c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01 46
+c0 2c 00 00 02 00 01 00 07 e9 00 00 04 01 4a c0
+2c 00 00 02 00 01 00 07 e9 00 00 04 01 47 c0 2c
+00 00 02 00 01 00 07 e9 00 00 04 01 48 c0 2c
diff --git a/src/lib/cc/data.h b/src/lib/cc/data.h
index 4f121d5..0a363f4 100644
--- a/src/lib/cc/data.h
+++ b/src/lib/cc/data.h
@@ -222,6 +222,7 @@ public:
/// Sets the ElementPtr at the given key
/// \param name The key of the Element to set
+ /// \param element The ElementPtr to set at the given key.
virtual void set(const std::string& name, ConstElementPtr element);
/// Remove the ElementPtr at the given key
@@ -315,10 +316,11 @@ public:
/// Creates an Element from the given input stream, where we keep
/// track of the location in the stream for error reporting.
///
- /// \param in The string to parse the element from
+ /// \param in The string to parse the element from.
+ /// \param file The input file name.
/// \param line A reference to the int where the function keeps
/// track of the current line.
- /// \param line A reference to the int where the function keeps
+ /// \param pos A reference to the int where the function keeps
/// track of the current position within the current line.
/// \return An ElementPtr that contains the element(s) specified
/// in the given input stream.
@@ -548,18 +550,18 @@ void merge(ElementPtr element, ConstElementPtr other);
///
/// \brief Insert the Element as a string into stream.
///
-/// This method converts the \c ElemetPtr into a string with
+/// This method converts the \c ElementPtr into a string with
/// \c Element::str() and inserts it into the
/// output stream \c out.
///
/// This function overloads the global operator<< to behave as described in
/// ostream::operator<< but applied to \c ElementPtr objects.
///
-/// \param os A \c std::ostream object on which the insertion operation is
+/// \param out A \c std::ostream object on which the insertion operation is
/// performed.
/// \param e The \c ElementPtr object to insert.
/// \return A reference to the same \c std::ostream object referenced by
-/// parameter \c os after the insertion operation.
+/// parameter \c out after the insertion operation.
std::ostream& operator<<(std::ostream& out, const Element& e);
bool operator==(const Element& a, const Element& b);
diff --git a/src/lib/cc/session.h b/src/lib/cc/session.h
index 6d8696d..e91dd76 100644
--- a/src/lib/cc/session.h
+++ b/src/lib/cc/session.h
@@ -99,7 +99,7 @@ namespace isc {
/// \brief Sets the default timeout for blocking reads
/// in this session to the given number of milliseconds
/// \param milliseconds the timeout for blocking reads in
- /// milliseconds, if this is set to 0, reads will block
+ /// milliseconds; if this is set to 0, reads will block
/// forever.
virtual void setTimeout(size_t milliseconds) = 0;
diff --git a/src/lib/config/module_spec.cc b/src/lib/config/module_spec.cc
index fd07dde..1621fe3 100644
--- a/src/lib/config/module_spec.cc
+++ b/src/lib/config/module_spec.cc
@@ -372,15 +372,18 @@ ModuleSpec::validateSpecList(ConstElementPtr spec, ConstElementPtr data,
BOOST_FOREACH(maptype m, data->mapValue()) {
bool found = false;
- BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
- if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
- found = true;
+ // Ignore 'version' as a config element
+ if (m.first.compare("version") != 0) {
+ BOOST_FOREACH(ConstElementPtr cur_spec_el, spec->listValue()) {
+ if (cur_spec_el->get("item_name")->stringValue().compare(m.first) == 0) {
+ found = true;
+ }
}
- }
- if (!found) {
- validated = false;
- if (errors) {
- errors->add(Element::create("Unknown item " + m.first));
+ if (!found) {
+ validated = false;
+ if (errors) {
+ errors->add(Element::create("Unknown item " + m.first));
+ }
}
}
}
diff --git a/src/lib/config/module_spec.h b/src/lib/config/module_spec.h
index 0e06a62..ab6e273 100644
--- a/src/lib/config/module_spec.h
+++ b/src/lib/config/module_spec.h
@@ -53,6 +53,8 @@ namespace isc { namespace config {
/// Create a \c ModuleSpec instance with the given data as
/// the specification
/// \param e The Element containing the data specification
+ /// \param check If false, the module specification in the file
+ /// is not checked to be of the correct form.
explicit ModuleSpec(isc::data::ConstElementPtr e,
const bool check = true)
throw(ModuleSpecError);
@@ -86,6 +88,8 @@ namespace isc { namespace config {
// configuration specification
/// Validates the given configuration data for this specification.
/// \param data The base \c Element of the data to check
+ /// \param full If true, all non-optional configuration parameters
+ /// must be specified.
/// \return true if the data conforms to the specification,
/// false otherwise.
bool validateConfig(isc::data::ConstElementPtr data,
diff --git a/src/lib/config/tests/module_spec_unittests.cc b/src/lib/config/tests/module_spec_unittests.cc
index 59f5459..1b43350 100644
--- a/src/lib/config/tests/module_spec_unittests.cc
+++ b/src/lib/config/tests/module_spec_unittests.cc
@@ -162,6 +162,10 @@ TEST(ModuleSpec, DataValidation) {
EXPECT_FALSE(dataTest(dd, "data22_8.data"));
EXPECT_FALSE(dataTest(dd, "data22_9.data"));
+ // Test if "version" is allowed in config data
+ // (same data as 22_7, but added "version")
+ EXPECT_TRUE(dataTest(dd, "data22_10.data"));
+
ElementPtr errors = Element::createList();
EXPECT_FALSE(dataTestWithErrors(dd, "data22_8.data", errors));
EXPECT_EQ("[ \"Type mismatch\" ]", errors->str());
diff --git a/src/lib/config/tests/testdata/Makefile.am b/src/lib/config/tests/testdata/Makefile.am
index a16b106..94c087d 100644
--- a/src/lib/config/tests/testdata/Makefile.am
+++ b/src/lib/config/tests/testdata/Makefile.am
@@ -21,6 +21,7 @@ EXTRA_DIST += data22_6.data
EXTRA_DIST += data22_7.data
EXTRA_DIST += data22_8.data
EXTRA_DIST += data22_9.data
+EXTRA_DIST += data22_10.data
EXTRA_DIST += spec1.spec
EXTRA_DIST += spec2.spec
EXTRA_DIST += spec3.spec
diff --git a/src/lib/config/tests/testdata/data22_10.data b/src/lib/config/tests/testdata/data22_10.data
new file mode 100644
index 0000000..fed4001
--- /dev/null
+++ b/src/lib/config/tests/testdata/data22_10.data
@@ -0,0 +1,11 @@
+{
+ "version": 123,
+ "value1": 1,
+ "value2": 2.3,
+ "value3": true,
+ "value4": "foo",
+ "value5": [ 1, 2, 3 ],
+ "value6": { "v61": "bar", "v62": true },
+ "value8": [ { "a": "d" }, { "a": "e" } ],
+ "value9": { "v91": "hi", "v92": { "v92a": "Hi", "v92b": 3 } }
+}
diff --git a/src/lib/datasrc/data_source.cc b/src/lib/datasrc/data_source.cc
index 829d1fc..c9819bb 100644
--- a/src/lib/datasrc/data_source.cc
+++ b/src/lib/datasrc/data_source.cc
@@ -48,6 +48,28 @@ using namespace std;
using namespace isc::dns;
using namespace isc::dns::rdata;
+namespace {
+
+struct MatchRRsetForType {
+ MatchRRsetForType(const RRType rrtype) : rrtype_(rrtype) {}
+ bool operator()(RRsetPtr rrset) {
+ return (rrset->getType() == rrtype_);
+ }
+ const RRType rrtype_;
+};
+
+// This is a helper to retrieve a specified RR type of RRset from RRsetList.
+// In our case the data source search logic should ensure that the class is
+// valid. We use this find logic of our own so that we can support both
+// specific RR class queries (normal case) and class ANY queries.
+RRsetPtr
+findRRsetFromList(RRsetList& list, const RRType rrtype) {
+ RRsetList::iterator it(find_if(list.begin(), list.end(),
+ MatchRRsetForType(rrtype)));
+ return (it != list.end() ? *it : RRsetPtr());
+}
+}
+
namespace isc {
namespace datasrc {
@@ -129,7 +151,7 @@ synthesizeCname(QueryTaskPtr task, RRsetPtr rrset, RRsetList& target) {
const generic::DNAME& dname = dynamic_cast<const generic::DNAME&>(rd);
const Name& dname_target(dname.getDname());
- RRsetPtr cname(new RRset(task->qname, task->qclass, RRType::CNAME(),
+ RRsetPtr cname(new RRset(task->qname, rrset->getClass(), RRType::CNAME(),
rrset->getTTL()));
const int qnlen = task->qname.getLabelCount();
@@ -189,6 +211,19 @@ checkCache(QueryTask& task, RRsetList& target) {
rrsets.addRRset(rrset);
target.append(rrsets);
}
+
+ // Reset the referral flag and treat CNAME as "not found".
+ // This emulates the behavior of the sqlite3 data source.
+ // XXX: this is not ideal in that the responsibility for handling
+ // operation specific cases is spread over various classes at
+ // different abstraction levels. For longer terms we should
+ // revisit the whole datasource/query design, and clarify this
+ // point better.
+ flags &= ~DataSrc::REFERRAL;
+ if ((flags & DataSrc::CNAME_FOUND) != 0) {
+ flags &= ~DataSrc::CNAME_FOUND;
+ flags |= DataSrc::TYPE_NOT_FOUND;
+ }
task.flags = flags;
return (true);
}
@@ -556,17 +591,17 @@ hasDelegation(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo) {
// Found a referral while getting answer data;
// send a delegation.
if (found) {
- RRsetPtr r = ref.findRRset(RRType::DNAME(), q.qclass());
+ RRsetPtr r = findRRsetFromList(ref, RRType::DNAME());
if (r != NULL) {
RRsetList syn;
addToMessage(q, Message::SECTION_ANSWER, r);
q.message().setHeaderFlag(Message::HEADERFLAG_AA);
synthesizeCname(task, r, syn);
if (syn.size() == 1) {
- addToMessage(q, Message::SECTION_ANSWER,
- syn.findRRset(RRType::CNAME(), q.qclass()));
- chaseCname(q, task, syn.findRRset(RRType::CNAME(),
- q.qclass()));
+ RRsetPtr cname_rrset = findRRsetFromList(syn,
+ RRType::CNAME());
+ addToMessage(q, Message::SECTION_ANSWER, cname_rrset);
+ chaseCname(q, task, cname_rrset);
return (true);
}
}
@@ -599,7 +634,7 @@ addSOA(Query& q, ZoneInfo& zoneinfo) {
}
addToMessage(q, Message::SECTION_AUTHORITY,
- soa.findRRset(RRType::SOA(), q.qclass()));
+ findRRsetFromList(soa, RRType::SOA()));
return (DataSrc::SUCCESS);
}
@@ -611,7 +646,7 @@ addNSEC(Query& q, const Name& name, ZoneInfo& zoneinfo) {
RETERR(doQueryTask(newtask, zoneinfo, nsec));
if (newtask.flags == 0) {
addToMessage(q, Message::SECTION_AUTHORITY,
- nsec.findRRset(RRType::NSEC(), q.qclass()));
+ findRRsetFromList(nsec, RRType::NSEC()));
}
return (DataSrc::SUCCESS);
@@ -815,7 +850,7 @@ tryWildcard(Query& q, QueryTaskPtr task, ZoneInfo& zoneinfo, bool& found) {
// match the qname), and then continue as if this were a normal
// answer: if a CNAME, chase the target, otherwise add authority.
if (cname) {
- RRsetPtr rrset = wild.findRRset(RRType::CNAME(), q.qclass());
+ RRsetPtr rrset = findRRsetFromList(wild, RRType::CNAME());
if (rrset != NULL) {
rrset->setName(task->qname);
addToMessage(q, Message::SECTION_ANSWER, rrset);
@@ -910,7 +945,7 @@ DataSrc::doQuery(Query& q) {
((task->qtype == RRType::NSEC() ||
task->qtype == RRType::DS() ||
task->qtype == RRType::DNAME()) &&
- data.findRRset(task->qtype, task->qclass)))) {
+ findRRsetFromList(data, task->qtype)))) {
task->flags &= ~REFERRAL;
}
@@ -935,9 +970,8 @@ DataSrc::doQuery(Query& q) {
// Add the NS records for the enclosing zone to
// the authority section.
RRsetList auth;
- const DataSrc* ds = zoneinfo.getDataSource();
- if (!refQuery(q, Name(*zonename), zoneinfo, auth) ||
- !auth.findRRset(RRType::NS(), ds->getClass())) {
+ if (!refQuery(q, Name(*zonename), zoneinfo, auth) ||
+ !findRRsetFromList(auth, RRType::NS())) {
isc_throw(DataSourceError,
"NS RR not found in " << *zonename << "/" <<
q.qclass());
@@ -970,7 +1004,7 @@ DataSrc::doQuery(Query& q) {
} else if ((task->flags & CNAME_FOUND) != 0) {
// The qname node contains a CNAME. Add a new task to the
// queue to look up its target.
- RRsetPtr rrset = data.findRRset(RRType::CNAME(), q.qclass());
+ RRsetPtr rrset = findRRsetFromList(data, RRType::CNAME());
if (rrset != NULL) {
addToMessage(q, task->section, rrset);
chaseCname(q, task, rrset);
@@ -1000,6 +1034,13 @@ DataSrc::doQuery(Query& q) {
continue;
} else if ((task->flags & (NAME_NOT_FOUND|TYPE_NOT_FOUND)) != 0) {
// No data found at this qname/qtype.
+
+ // If we were looking for additional data, we should simply
+ // ignore this result.
+ if (task->state == QueryTask::GETADDITIONAL) {
+ continue;
+ }
+
// If we were looking for answer data, not additional,
// and the name was not found, we need to find out whether
// there are any relevant wildcards.
diff --git a/src/lib/datasrc/memory_datasrc.h b/src/lib/datasrc/memory_datasrc.h
index 5a2c74e..99bb4e8 100644
--- a/src/lib/datasrc/memory_datasrc.h
+++ b/src/lib/datasrc/memory_datasrc.h
@@ -289,7 +289,7 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
- /// - \c zone: A <Boost> shared pointer to the found \c Zone object if one
+ /// - \c zone: A "Boost" shared pointer to the found \c Zone object if one
// is found; otherwise \c NULL.
///
/// This method never throws an exception.
diff --git a/src/lib/datasrc/rbtree.h b/src/lib/datasrc/rbtree.h
index bd04066..03a6967 100644
--- a/src/lib/datasrc/rbtree.h
+++ b/src/lib/datasrc/rbtree.h
@@ -533,12 +533,9 @@ private:
private:
// The max label count for one domain name is Name::MAX_LABELS (128).
- // Since each node in rbtree stores at least one label, and the root
- // name always shares the same level with some others (which means
- // all top level nodes except the one for the root name contain at least
- // two labels), the possible maximum level is MAX_LABELS - 1.
- // It's also the possible maximum nodes stored in a chain.
- const static int RBT_MAX_LEVEL = isc::dns::Name::MAX_LABELS - 1;
+ // Since each node in rbtree stores at least one label, it's also equal
+ // to the possible maximum level.
+ const static int RBT_MAX_LEVEL = isc::dns::Name::MAX_LABELS;
int node_count_;
const RBNode<T>* nodes_[RBT_MAX_LEVEL];
@@ -999,8 +996,15 @@ RBTree<T>::find(const isc::dns::Name& target_name,
const int common_label_count =
node_path.last_comparison_.getCommonLabels();
// If the common label count is 1, there is no common label between
- // the two names, except the trailing "dot".
- if (common_label_count == 1) {
+ // the two names, except the trailing "dot". In this case the two
+ // sequences of labels have essentially no hierarchical
+ // relationship in terms of matching, so we should continue the
+ // binary search. One important exception is when the node
+ // represents the root name ("."), in which case the comparison
+ // result must indeed be considered subdomain matching. (We use
+ // getLength() to check if the name is root, which is an equivalent
+ // but cheaper way).
+ if (common_label_count == 1 && node->name_.getLength() != 1) {
node = (node_path.last_comparison_.getOrder() < 0) ?
node->left_ : node->right_;
} else if (relation == isc::dns::NameComparisonResult::SUBDOMAIN) {
@@ -1093,7 +1097,8 @@ RBTree<T>::insert(const isc::dns::Name& target_name, RBNode<T>** new_node) {
return (ALREADYEXISTS);
} else {
const int common_label_count = compare_result.getCommonLabels();
- if (common_label_count == 1) {
+ // Note: see find() for the check of getLength().
+ if (common_label_count == 1 && current->name_.getLength() != 1) {
parent = current;
order = compare_result.getOrder();
current = order < 0 ? current->left_ : current->right_;
diff --git a/src/lib/datasrc/result.h b/src/lib/datasrc/result.h
index 201cbcc..f7ca363 100644
--- a/src/lib/datasrc/result.h
+++ b/src/lib/datasrc/result.h
@@ -1,4 +1,3 @@
-// Copyright (C) 2010 CZ NIC
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
diff --git a/src/lib/datasrc/tests/Makefile.am b/src/lib/datasrc/tests/Makefile.am
index 3c36e2d..f09b4b7 100644
--- a/src/lib/datasrc/tests/Makefile.am
+++ b/src/lib/datasrc/tests/Makefile.am
@@ -31,6 +31,7 @@ run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(SQLITE_LIBS)
+run_unittests_LDADD += $(top_builddir)/src/lib/testutils/libtestutils.la
run_unittests_LDADD += $(top_builddir)/src/lib/datasrc/libdatasrc.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
diff --git a/src/lib/datasrc/tests/datasrc_unittest.cc b/src/lib/datasrc/tests/datasrc_unittest.cc
index bff7949..810aef9 100644
--- a/src/lib/datasrc/tests/datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/datasrc_unittest.cc
@@ -38,6 +38,7 @@
#include <datasrc/sqlite3_datasrc.h>
#include <datasrc/static_datasrc.h>
+#include <testutils/dnsmessage_test.h>
#include <dns/tests/unittest_util.h>
#include <datasrc/tests/test_datasrc.h>
@@ -47,6 +48,7 @@ using namespace isc::dns;
using namespace isc::dns::rdata;
using namespace isc::datasrc;
using namespace isc::data;
+using namespace isc::testutils;
namespace {
ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
@@ -54,7 +56,9 @@ ConstElementPtr SQLITE_DBFILE_EXAMPLE = Element::fromJSON(
class DataSrcTest : public ::testing::Test {
protected:
- DataSrcTest() : obuffer(0), renderer(obuffer), msg(Message::PARSE) {
+ DataSrcTest() : obuffer(0), renderer(obuffer), msg(Message::PARSE),
+ opcodeval(Opcode::QUERY().getCode()), qid(0)
+ {
DataSrcPtr sql3_source = DataSrcPtr(new Sqlite3DataSrc);
sql3_source->init(SQLITE_DBFILE_EXAMPLE);
DataSrcPtr test_source = DataSrcPtr(new TestDataSrc);
@@ -66,54 +70,46 @@ protected:
}
void QueryCommon(const RRClass& qclass);
void createAndProcessQuery(const Name& qname, const RRClass& qclass,
- const RRType& qtype);
+ const RRType& qtype, bool need_dnssec);
HotCache cache;
MetaDataSrc meta_source;
OutputBuffer obuffer;
MessageRenderer renderer;
Message msg;
+ const uint16_t opcodeval;
+ qid_t qid;
};
void
-performQuery(DataSrc& data_source, HotCache& cache, Message& message) {
+performQuery(DataSrc& data_source, HotCache& cache, Message& message,
+ bool need_dnssec = true)
+{
message.setHeaderFlag(Message::HEADERFLAG_AA);
message.setRcode(Rcode::NOERROR());
- Query q(message, cache, true);
+ Query q(message, cache, need_dnssec);
data_source.doQuery(q);
}
void
DataSrcTest::createAndProcessQuery(const Name& qname, const RRClass& qclass,
- const RRType& qtype)
+ const RRType& qtype,
+ bool need_dnssec = true)
{
msg.makeResponse();
msg.setOpcode(Opcode::QUERY());
msg.addQuestion(Question(qname, qclass, qtype));
msg.setHeaderFlag(Message::HEADERFLAG_RD);
- performQuery(meta_source, cache, msg);
-}
-
-void
-headerCheck(const Message& message, const Rcode& rcode, const bool qrflag,
- const bool aaflag, const bool rdflag, const unsigned int ancount,
- const unsigned int nscount, const unsigned int arcount)
-{
- EXPECT_EQ(rcode, message.getRcode());
- EXPECT_EQ(qrflag, message.getHeaderFlag(Message::HEADERFLAG_QR));
- EXPECT_EQ(aaflag, message.getHeaderFlag(Message::HEADERFLAG_AA));
- EXPECT_EQ(rdflag, message.getHeaderFlag(Message::HEADERFLAG_RD));
-
- EXPECT_EQ(ancount, message.getRRCount(Message::SECTION_ANSWER));
- EXPECT_EQ(nscount, message.getRRCount(Message::SECTION_AUTHORITY));
- EXPECT_EQ(arcount, message.getRRCount(Message::SECTION_ADDITIONAL));
+ qid = msg.getQid();
+ performQuery(meta_source, cache, msg, need_dnssec);
}
void
DataSrcTest::QueryCommon(const RRClass& qclass) {
createAndProcessQuery(Name("www.example.com"), qclass, RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -163,12 +159,8 @@ TEST_F(DataSrcTest, Query) {
// should be the same as "NxZone".
TEST_F(DataSrcTest, QueryClassMismatch) {
createAndProcessQuery(Name("www.example.com"), RRClass::CH(), RRType::A());
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
-
- EXPECT_EQ(Rcode::REFUSED(), msg.getRcode());
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_QR));
- EXPECT_FALSE(msg.getHeaderFlag(Message::HEADERFLAG_AA));
- EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_RD));
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval, QR_FLAG | RD_FLAG,
+ 1, 0, 0, 0);
}
// Query class of any should match the first data source.
@@ -176,10 +168,64 @@ TEST_F(DataSrcTest, QueryClassAny) {
QueryCommon(RRClass::ANY());
}
+TEST_F(DataSrcTest, queryClassAnyNegative) {
+ // There was a bug where Class ANY query triggered a crash due to NULL
+ // pointer dereference. This test checks that condition.
+
+ // NXDOMAIN case
+ createAndProcessQuery(Name("notexistent.example.com"), RRClass::ANY(),
+ RRType::A());
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
+
+ // NXRRSET case
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("www.example.com"), RRClass::ANY(),
+ RRType::TXT());
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 4, 0);
+}
+
+TEST_F(DataSrcTest, queryClassAnyDNAME) {
+ // Class ANY query that would match a DNAME. Everything including the
+ // synthesized CNAME should be the same as the response to class IN query.
+ createAndProcessQuery(Name("www.dname.example.com"), RRClass::ANY(),
+ RRType::A(), false);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 3, 3, 3);
+ rrsetsCheck("dname.example.com. 3600 IN DNAME sql1.example.com.\n"
+ "www.dname.example.com. 3600 IN CNAME www.sql1.example.com.\n"
+ "www.sql1.example.com. 3600 IN A 192.0.2.2\n",
+ msg.beginSection(Message::SECTION_ANSWER),
+ msg.endSection(Message::SECTION_ANSWER));
+
+ // Also check the case of explicit DNAME query.
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("dname.example.com"), RRClass::ANY(),
+ RRType::DNAME(), false);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 1, 3, 3);
+ rrsetsCheck("dname.example.com. 3600 IN DNAME sql1.example.com.\n",
+ msg.beginSection(Message::SECTION_ANSWER),
+ msg.endSection(Message::SECTION_ANSWER));
+}
+
+TEST_F(DataSrcTest, queryClassAnyCNAME) {
+ // Similar test for CNAME
+ createAndProcessQuery(Name("foo.example.com"), RRClass::ANY(),
+ RRType::A(), false);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 1, 0, 0);
+ rrsetsCheck("foo.example.com. 3600 IN CNAME cnametest.example.net.\n",
+ msg.beginSection(Message::SECTION_ANSWER),
+ msg.endSection(Message::SECTION_ANSWER));
+}
+
TEST_F(DataSrcTest, NSQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -201,7 +247,8 @@ TEST_F(DataSrcTest, NSQuery) {
TEST_F(DataSrcTest, DuplicateQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -221,7 +268,8 @@ TEST_F(DataSrcTest, DuplicateQuery) {
msg.clear(Message::PARSE);
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 0, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 0, 6);
rit = msg.beginSection(Message::SECTION_ANSWER);
rrset = *rit;
@@ -242,7 +290,8 @@ TEST_F(DataSrcTest, DuplicateQuery) {
TEST_F(DataSrcTest, DNSKEYQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::DNSKEY());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -257,7 +306,8 @@ TEST_F(DataSrcTest, DNSKEYQuery) {
TEST_F(DataSrcTest, DNSKEYDuplicateQuery) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::DNSKEY());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -279,7 +329,8 @@ TEST_F(DataSrcTest, NxRRset) {
createAndProcessQuery(Name("example.com"), RRClass::IN(),
RRType::PTR());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 4, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 4, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -291,7 +342,8 @@ TEST_F(DataSrcTest, Nxdomain) {
createAndProcessQuery(Name("glork.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 6, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -301,11 +353,46 @@ TEST_F(DataSrcTest, Nxdomain) {
// XXX: check for other authority section answers
}
+TEST_F(DataSrcTest, NxdomainAfterSOAQuery) {
+ // There was a bug where once SOA RR is stored in the hot spot cache
+ // subsequent negative search fails due to "missing SOA". This test
+ // checks that situation.
+
+ // First, run the scenario with disabling the cache.
+ cache.setEnabled(false);
+ createAndProcessQuery(Name("example.com"), RRClass::IN(),
+ RRType::SOA());
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("notexistent.example.com"), RRClass::IN(),
+ RRType::A());
+ {
+ SCOPED_TRACE("NXDOMAIN after SOA, without hot spot cache");
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
+ }
+
+ // Then enable the cache and perform the same queries. This should
+ // produce the same result.
+ cache.setEnabled(true);
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("example.com"), RRClass::IN(),
+ RRType::SOA());
+ msg.clear(Message::PARSE);
+ createAndProcessQuery(Name("notexistent.example.com"), RRClass::IN(),
+ RRType::A());
+ {
+ SCOPED_TRACE("NXDOMAIN after SOA, without hot spot cache");
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 6, 0);
+ }
+}
+
TEST_F(DataSrcTest, NxZone) {
createAndProcessQuery(Name("spork.example"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
EXPECT_EQ(Rcode::REFUSED(), msg.getRcode());
EXPECT_TRUE(msg.getHeaderFlag(Message::HEADERFLAG_QR));
@@ -317,7 +404,8 @@ TEST_F(DataSrcTest, Wildcard) {
createAndProcessQuery(Name("www.wild.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 6, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 6, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -369,7 +457,8 @@ TEST_F(DataSrcTest, WildcardNodata) {
// returns NOERROR
createAndProcessQuery(Name("www.wild.example.com"), RRClass::IN(),
RRType::AAAA());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 2, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 2, 0);
}
TEST_F(DataSrcTest, DISABLED_WildcardAgainstMultiLabel) {
@@ -377,72 +466,42 @@ TEST_F(DataSrcTest, DISABLED_WildcardAgainstMultiLabel) {
// a single label), and it should result in NXDOMAIN.
createAndProcessQuery(Name("www.xxx.wild.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, WildcardCname) {
// Check that wildcard answers containing CNAMES are followed
- // correctly
- createAndProcessQuery(Name("www.wild2.example.com"), RRClass::IN(),
- RRType::A());
-
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 6, 6);
-
- RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
- RRsetPtr rrset = *rit;
- EXPECT_EQ(Name("www.wild2.example.com"), rrset->getName());
- EXPECT_EQ(RRType::CNAME(), rrset->getType());
- EXPECT_EQ(RRClass::IN(), rrset->getClass());
-
- RdataIteratorPtr it = rrset->getRdataIterator();
- EXPECT_EQ("www.example.com.", it->getCurrent().toText());
- it->next();
- EXPECT_TRUE(it->isLast());
-
- ++rit;
- ++rit;
- rrset = *rit;
- EXPECT_EQ(Name("www.example.com"), rrset->getName());
- EXPECT_EQ(RRType::A(), rrset->getType());
- EXPECT_EQ(RRClass::IN(), rrset->getClass());
-
- it = rrset->getRdataIterator();
- EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
- it->next();
- EXPECT_TRUE(it->isLast());
-
- rit = msg.beginSection(Message::SECTION_AUTHORITY);
- rrset = *rit;
- EXPECT_EQ(Name("*.wild2.example.com"), rrset->getName());
- EXPECT_EQ(RRType::NSEC(), rrset->getType());
- EXPECT_EQ(RRClass::IN(), rrset->getClass());
- ++rit;
- ++rit;
-
- rrset = *rit;
- EXPECT_EQ(Name("example.com"), rrset->getName());
- EXPECT_EQ(RRType::NS(), rrset->getType());
- EXPECT_EQ(RRClass::IN(), rrset->getClass());
-
- it = rrset->getRdataIterator();
- EXPECT_EQ("dns01.example.com.", it->getCurrent().toText());
- it->next();
- EXPECT_EQ("dns02.example.com.", it->getCurrent().toText());
- it->next();
- EXPECT_EQ("dns03.example.com.", it->getCurrent().toText());
- it->next();
- EXPECT_TRUE(it->isLast());
-
- rit = msg.beginSection(Message::SECTION_ADDITIONAL);
- rrset = *rit;
- EXPECT_EQ(Name("dns01.example.com"), rrset->getName());
- EXPECT_EQ(RRType::A(), rrset->getType());
- EXPECT_EQ(RRClass::IN(), rrset->getClass());
-
- it = rrset->getRdataIterator();
- EXPECT_EQ("192.0.2.1", it->getCurrent().toText());
- it->next();
- EXPECT_TRUE(it->isLast());
+ // correctly. It should result in the same response for both
+ // class IN and ANY queries.
+ const RRClass classes[2] = { RRClass::IN(), RRClass::ANY() };
+
+ for (int i = 0; i < sizeof(classes) / sizeof(classes[0]); ++i) {
+ SCOPED_TRACE("Wildcard + CNAME test for class " + classes[i].toText());
+
+ msg.clear(Message::PARSE);
+
+ createAndProcessQuery(Name("www.wild2.example.com"), classes[i],
+ RRType::A(), false);
+
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 3, 3);
+
+ rrsetsCheck("www.wild2.example.com. 3600 IN CNAME www.example.com\n"
+ "www.example.com. 3600 IN A 192.0.2.1\n",
+ msg.beginSection(Message::SECTION_ANSWER),
+ msg.endSection(Message::SECTION_ANSWER));
+ rrsetsCheck("example.com. 3600 IN NS dns01.example.com.\n"
+ "example.com. 3600 IN NS dns02.example.com.\n"
+ "example.com. 3600 IN NS dns03.example.com.",
+ msg.beginSection(Message::SECTION_AUTHORITY),
+ msg.endSection(Message::SECTION_AUTHORITY));
+ rrsetsCheck("dns01.example.com. 3600 IN A 192.0.2.1\n"
+ "dns02.example.com. 3600 IN A 192.0.2.2\n"
+ "dns03.example.com. 3600 IN A 192.0.2.3",
+ msg.beginSection(Message::SECTION_ADDITIONAL),
+ msg.endSection(Message::SECTION_ADDITIONAL));
+ }
}
TEST_F(DataSrcTest, WildcardCnameNodata) {
@@ -450,7 +509,8 @@ TEST_F(DataSrcTest, WildcardCnameNodata) {
// data of this type.
createAndProcessQuery(Name("www.wild2.example.com"), RRClass::IN(),
RRType::AAAA());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -481,7 +541,8 @@ TEST_F(DataSrcTest, WildcardCnameNxdomain) {
// A wildcard containing a CNAME whose target does not exist
createAndProcessQuery(Name("www.wild3.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 6, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 6, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -518,7 +579,8 @@ TEST_F(DataSrcTest, AuthDelegation) {
createAndProcessQuery(Name("www.sql1.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -562,7 +624,8 @@ TEST_F(DataSrcTest, Dname) {
createAndProcessQuery(Name("www.dname.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 5, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 5, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -610,14 +673,16 @@ TEST_F(DataSrcTest, DnameExact) {
// confuse delegation processing.
createAndProcessQuery(Name("dname2.foo.example.org"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, Cname) {
createAndProcessQuery(Name("foo.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 0, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 0, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -626,7 +691,7 @@ TEST_F(DataSrcTest, Cname) {
EXPECT_EQ(RRClass::IN(), rrset->getClass());
RdataIteratorPtr it = rrset->getRdataIterator();
- EXPECT_EQ("cnametest.flame.org.", it->getCurrent().toText());
+ EXPECT_EQ("cnametest.example.net.", it->getCurrent().toText());
it->next();
EXPECT_TRUE(it->isLast());
}
@@ -635,7 +700,8 @@ TEST_F(DataSrcTest, CnameInt) {
createAndProcessQuery(Name("cname-int.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -661,7 +727,8 @@ TEST_F(DataSrcTest, CnameExt) {
createAndProcessQuery(Name("cname-ext.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 4, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 4, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -685,7 +752,8 @@ TEST_F(DataSrcTest, Delegation) {
createAndProcessQuery(Name("www.subzone.example.com"), RRClass::IN(),
RRType::A());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -714,7 +782,8 @@ TEST_F(DataSrcTest, NSDelegation) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::NS());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
@@ -750,7 +819,8 @@ TEST_F(DataSrcTest, NSECZonecut) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::NSEC());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 2, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 2, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -778,7 +848,8 @@ TEST_F(DataSrcTest, DNAMEZonecut) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::DNAME());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 5, 2);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 5, 2);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("subzone.example.com."), rrset->getName());
@@ -806,7 +877,8 @@ TEST_F(DataSrcTest, DS) {
createAndProcessQuery(Name("subzone.example.com"), RRClass::IN(),
RRType::DS());
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 3, 4, 6);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 3, 4, 6);
RRsetIterator rit = msg.beginSection(Message::SECTION_ANSWER);
RRsetPtr rrset = *rit;
@@ -847,7 +919,8 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
createAndProcessQuery(Name("sub.example.org"), RRClass::IN(),
RRType::NSEC());
- headerCheck(msg, Rcode::NOERROR(), true, false, true, 0, 1, 1);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 1, 1);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
ConstRRsetPtr rrset = *rit;
@@ -879,7 +952,8 @@ TEST_F(DataSrcTest, NSECZonecutOfNonsecureZone) {
TEST_F(DataSrcTest, RootDSQuery1) {
EXPECT_NO_THROW(createAndProcessQuery(Name("."), RRClass::IN(),
RRType::DS()));
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
}
// The same, but when we have the root zone
@@ -898,7 +972,8 @@ TEST_F(DataSrcTest, RootDSQuery2) {
// Make the query
EXPECT_NO_THROW(performQuery(*sql3_source, cache, msg));
- headerCheck(msg, Rcode::NOERROR(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
}
TEST_F(DataSrcTest, DSQueryFromCache) {
@@ -916,7 +991,8 @@ TEST_F(DataSrcTest, DSQueryFromCache) {
// returning refused is probably a bad behavior, but it's a different
// issue -- see Trac Ticket #306.
- headerCheck(msg, Rcode::REFUSED(), true, false, true, 0, 0, 0);
+ headerCheck(msg, qid, Rcode::REFUSED(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 0, 0);
}
// Non-existent name in the "static" data source. The purpose of this test
@@ -925,7 +1001,8 @@ TEST_F(DataSrcTest, DSQueryFromCache) {
TEST_F(DataSrcTest, StaticNxDomain) {
createAndProcessQuery(Name("www.version.bind"), RRClass::CH(),
RRType::TXT());
- headerCheck(msg, Rcode::NXDOMAIN(), true, true, true, 0, 1, 0);
+ headerCheck(msg, qid, Rcode::NXDOMAIN(), opcodeval,
+ QR_FLAG | AA_FLAG | RD_FLAG, 1, 0, 1, 0);
RRsetIterator rit = msg.beginSection(Message::SECTION_AUTHORITY);
RRsetPtr rrset = *rit;
EXPECT_EQ(Name("version.bind"), rrset->getName());
@@ -973,6 +1050,34 @@ TEST_F(DataSrcTest, noSOAZone) {
DataSourceError);
}
+TEST_F(DataSrcTest, apexCNAMEZone) {
+ // The query name doesn't exist in the best matching zone,
+ // and there's a CNAME at the apex (which is bogus), so query handling
+ // will fail due to missing SOA.
+ EXPECT_THROW(createAndProcessQuery(Name("notexist.apexcname.example"),
+ RRClass::IN(), RRType::A()),
+ DataSourceError);
+}
+
+TEST_F(DataSrcTest, incompleteGlue) {
+ // One of the NS names belong to a different zone (which is still
+ // authoritative), and the glue is missing in that zone. We should
+ // still return the existent glue.
+ // (nons.example is also broken in that it doesn't have apex NS, but
+ // that doesn't matter for this test)
+ createAndProcessQuery(Name("www.incompletechild.nons.example"),
+ RRClass::IN(), RRType::A());
+ headerCheck(msg, qid, Rcode::NOERROR(), opcodeval,
+ QR_FLAG | RD_FLAG, 1, 0, 2, 1);
+ rrsetsCheck("incompletechild.nons.example. 3600 IN NS ns.incompletechild.nons.example.\n"
+ "incompletechild.nons.example. 3600 IN NS nx.nosoa.example.",
+ msg.beginSection(Message::SECTION_AUTHORITY),
+ msg.endSection(Message::SECTION_AUTHORITY));
+ rrsetsCheck("ns.incompletechild.nons.example. 3600 IN A 192.0.2.1",
+ msg.beginSection(Message::SECTION_ADDITIONAL),
+ msg.endSection(Message::SECTION_ADDITIONAL));
+}
+
// currently fails
TEST_F(DataSrcTest, DISABLED_synthesizedCnameTooLong) {
// qname has the possible max length (255 octets). it matches a DNAME,
diff --git a/src/lib/datasrc/tests/memory_datasrc_unittest.cc b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
index 16d749c..83fbb58 100644
--- a/src/lib/datasrc/tests/memory_datasrc_unittest.cc
+++ b/src/lib/datasrc/tests/memory_datasrc_unittest.cc
@@ -1,5 +1,4 @@
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
-// Copyright (C) 2011 CZ NIC
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/datasrc/tests/rbtree_unittest.cc b/src/lib/datasrc/tests/rbtree_unittest.cc
index 23b25f4..b26a22b 100644
--- a/src/lib/datasrc/tests/rbtree_unittest.cc
+++ b/src/lib/datasrc/tests/rbtree_unittest.cc
@@ -284,21 +284,21 @@ TEST_F(RBTreeTest, chainLevel) {
EXPECT_EQ(1, chain.getLevelCount());
/*
- * Now creating a possibly deepest tree with MAX_LABELS - 1 levels.
+ * Now creating a possibly deepest tree with MAX_LABELS levels.
* it should look like:
+ * (.)
+ * |
* a
- * /|
- * (.)a
* |
* a
* : (MAX_LABELS - 1) "a"'s
*
* then confirm that find() for the deepest name succeeds without any
* disruption, and the resulting chain has the expected level.
- * Note that "a." and the root name (".") belong to the same level.
- * So the possible maximum level is MAX_LABELS - 1, not MAX_LABELS.
+ * Note that the root name (".") solely belongs to a single level,
+ * so the levels begin with 2.
*/
- for (unsigned int i = 1; i < Name::MAX_LABELS; ++i) {
+ for (unsigned int i = 2; i <= Name::MAX_LABELS; ++i) {
node_name = Name("a.").concatenate(node_name);
EXPECT_EQ(RBTree<int>::SUCCESS, tree.insert(node_name, &rbtnode));
RBTreeNodeChain<int> found_chain;
@@ -398,7 +398,7 @@ TEST_F(RBTreeTest, getLastComparedNode) {
EXPECT_EQ(static_cast<void*>(NULL), chain.getLastComparedNode());
chain.clear();
- const RBNode<int>* expected_node;
+ const RBNode<int>* expected_node = NULL;
// Exact match case. The returned node should be last compared.
EXPECT_EQ(RBTree<int>::EXACTMATCH,
@@ -523,4 +523,44 @@ TEST_F(RBTreeTest, swap) {
tree2.dumpTree(out);
ASSERT_EQ(str1.str(), out.str());
}
+
+// Matching in the "root zone" may be special (e.g. there's no parent,
+// any domain names should be considered a subdomain of it), so it makes
+// sense to test cases with the root zone explicitly.
+TEST_F(RBTreeTest, root) {
+ RBTree<int> root;
+ root.insert(Name::ROOT_NAME(), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(1)));
+
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ root.find(Name::ROOT_NAME(), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ // Insert a new name that better matches the query name. find() should
+ // find the better one.
+ root.insert(Name("com"), &rbtnode);
+ rbtnode->setData(RBNode<int>::NodeDataPtr(new int(2)));
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ // Perform the same tests for the tree that allows matching against empty
+ // nodes.
+ RBTree<int> root_emptyok(true);
+ root_emptyok.insert(Name::ROOT_NAME(), &rbtnode);
+ EXPECT_EQ(RBTree<int>::EXACTMATCH,
+ root_emptyok.find(Name::ROOT_NAME(), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root_emptyok.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+
+ root.insert(Name("com"), &rbtnode);
+ EXPECT_EQ(RBTree<int>::PARTIALMATCH,
+ root.find(Name("example.com"), &crbtnode));
+ EXPECT_EQ(rbtnode, crbtnode);
+}
}
diff --git a/src/lib/datasrc/tests/test_datasrc.cc b/src/lib/datasrc/tests/test_datasrc.cc
index 8145539..c18f0bd 100644
--- a/src/lib/datasrc/tests/test_datasrc.cc
+++ b/src/lib/datasrc/tests/test_datasrc.cc
@@ -154,7 +154,7 @@ const struct RRData example_com_records[] = {
{"*.wild3.example.com", "RRSIG", "NSEC 5 3 7200 20100410212307 20100311212307 33495 example.com. EuSzh6or8mbvwru2H7fyYeMpW6J8YZ528rabU38V/lMN0TdamghIuCneAvSNaZgwk2MSN1bWpZqB2kAipaM/ZI9/piLlTvVjjOQ8pjk0auwCEqT7Z7Qng3E92O9yVzO+WHT9QZn/fR6t60392In4IvcBGjZyjzQk8njIwbui xGA="},
// foo.example.com
- {"foo.example.com", "CNAME", "cnametest.flame.org"},
+ {"foo.example.com", "CNAME", "cnametest.example.net"},
{"foo.example.com", "RRSIG", "CNAME 5 3 3600 20100322084538 20100220084538 33495 example.com. DSqkLnsh0gCeCPVW/Q8viy9GNP+KHmFGfWqyVG1S6koBtGN/VQQ16M4PHZ9Zssmf/JcDVJNIhAChHPE2WJiaPCNGTprsaUshf1Q2vMPVnkrJKgDY8SVRYMptmT8eaT0gGri4KhqRoFpMT5OYfesybwDgfhFSQQAh6ps3bIUsy4o="},
{"foo.example.com", "NSEC", "mail.example.com. CNAME RRSIG NSEC"},
{"foo.example.com", "RRSIG", "NSEC 5 3 7200 20100322084538 20100220084538 33495 example.com. RTQwlSqui6StUYye1KCSOEr1d3irndWFqHBpwP7g7n+w8EDXJ8I7lYgwzHvlQt6BLAxe5fUDi7ct8M5hXvsm7FoWPZ5wXH+2/eJUCYxIw4vezKMkMwBP6M/YkJ2CMqY8DppYf60QaLDONQAr7AcK/naSyioeI5h6eaoVitUDMso="},
@@ -199,6 +199,7 @@ const struct RRData example_com_records[] = {
{NULL, NULL, NULL}
};
+
const struct RRData example_com_glue_records[] = {
{"ns1.subzone.example.com", "A", "192.0.2.1"},
{"ns2.subzone.example.com", "A", "192.0.2.2"},
@@ -247,6 +248,20 @@ const struct RRData nons_example_records[] = {
"1234 3600 1800 2419200 7200"},
{"www.nons.example", "A", "192.0.2.1"},
{"ns.nons.example", "A", "192.0.2.2"},
+
+ // One of the NS names is intentionally non existent in the zone it belongs
+ // to. This delegation is used to see if we still return the NS and the
+ // existent glue.
+ // (These are not relevant to test the case for the "no NS" case. We use
+ // this zone to minimize the number of test zones)
+ {"incompletechild.nons.example", "NS", "ns.incompletechild.nons.example"},
+ {"incompletechild.nons.example", "NS", "nx.nosoa.example"},
+
+ {NULL, NULL, NULL}
+};
+
+const struct RRData nons_example_glue_records[] = {
+ {"ns.incompletechild.nons.example", "A", "192.0.2.1"},
{NULL, NULL, NULL}
};
@@ -273,6 +288,18 @@ const struct RRData nosoa_example_records[] = {
};
//
+// zone data for apexcname.example.
+//
+const struct RRData apexcname_example_records[] = {
+ {"apexcname.example", "CNAME", "canonical.apexcname.example"},
+ {"canonical.apexcname.example", "SOA",
+ "master.apexcname.example "
+ "admin.apexcname.example. 1234 3600 1800 2419200 7200"},
+ {NULL, NULL, NULL}
+};
+
+
+//
// empty data set, for convenience.
//
const struct RRData empty_records[] = {
@@ -286,9 +313,10 @@ const struct ZoneData zone_data[] = {
{ "example.com", "IN", example_com_records, example_com_glue_records },
{ "sql1.example.com", "IN", sql1_example_com_records, empty_records },
{ "loop.example", "IN", loop_example_records, empty_records },
- { "nons.example", "IN", nons_example_records, empty_records },
+ { "nons.example", "IN", nons_example_records, nons_example_glue_records },
{ "nons-dname.example", "IN", nonsdname_example_records, empty_records },
- { "nosoa.example", "IN", nosoa_example_records, empty_records }
+ { "nosoa.example", "IN", nosoa_example_records, empty_records },
+ { "apexcname.example", "IN", nosoa_example_records, empty_records }
};
const size_t NUM_ZONES = sizeof(zone_data) / sizeof(zone_data[0]);
diff --git a/src/lib/datasrc/zone.h b/src/lib/datasrc/zone.h
index f70274f..1252c94 100644
--- a/src/lib/datasrc/zone.h
+++ b/src/lib/datasrc/zone.h
@@ -1,4 +1,3 @@
-// Copyright (C) 2010 CZ NIC
// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
diff --git a/src/lib/datasrc/zonetable.h b/src/lib/datasrc/zonetable.h
index 0ca80b0..5b873d1 100644
--- a/src/lib/datasrc/zonetable.h
+++ b/src/lib/datasrc/zonetable.h
@@ -107,7 +107,7 @@ public:
/// - \c result::PARTIALMATCH: A zone whose origin is a
/// super domain of \c name is found (but there is no exact match)
/// - \c result::NOTFOUND: For all other cases.
- /// - \c zone: A <Boost> shared pointer to the found \c Zone object if one
+ /// - \c zone: A "Boost" shared pointer to the found \c Zone object if one
/// is found; otherwise \c NULL.
///
/// This method never throws an exception.
diff --git a/src/lib/dns/buffer.h b/src/lib/dns/buffer.h
index c824154..46a9bf4 100644
--- a/src/lib/dns/buffer.h
+++ b/src/lib/dns/buffer.h
@@ -15,6 +15,8 @@
#ifndef __BUFFER_H
#define __BUFFER_H 1
+#include <stdlib.h>
+#include <cstring>
#include <vector>
#include <string.h>
@@ -286,15 +288,58 @@ public:
/// \brief Constructor from the initial size of the buffer.
///
/// \param len The initial length of the buffer in bytes.
- OutputBuffer(size_t len) { data_.reserve(len); }
+ OutputBuffer(size_t len) :
+ buffer_(NULL),
+ size_(0),
+ allocated_(len)
+ {
+ // We use malloc and free instead of C++ new[] and delete[].
+ // This way we can use realloc, which may in fact do it without a copy.
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL && len != 0) {
+ throw std::bad_alloc();
+ }
+ }
+
+ /// \brief Copy constructor
+ OutputBuffer(const OutputBuffer& other) :
+ buffer_(NULL),
+ size_(other.size_),
+ allocated_(other.allocated_)
+ {
+ buffer_ = static_cast<uint8_t*>(malloc(allocated_));
+ if (buffer_ == NULL && allocated_ != 0) {
+ throw std::bad_alloc();
+ }
+ memcpy(buffer_, other.buffer_, size_);
+ }
+
+ /// \brief Destructor
+ ~ OutputBuffer() {
+ free(buffer_);
+ }
//@}
+ /// \brief Assignment operator
+ OutputBuffer& operator =(const OutputBuffer& other) {
+ uint8_t* newbuff(static_cast<uint8_t*>(malloc(other.allocated_)));
+ if (newbuff == NULL && other.allocated_ != 0) {
+ throw std::bad_alloc();
+ }
+ free(buffer_);
+ buffer_ = newbuff;
+ size_ = other.size_;
+ allocated_ = other.allocated_;
+ memcpy(buffer_, other.buffer_, size_);
+ return (*this);
+ }
+
///
/// \name Getter Methods
///
//@{
/// \brief Return the current capacity of the buffer.
- size_t getCapacity() const { return (data_.capacity()); }
+ size_t getCapacity() const { return (allocated_); }
/// \brief Return a pointer to the head of the data stored in the buffer.
///
/// The caller can assume that the subsequent \c getLength() bytes are
@@ -302,21 +347,21 @@ public:
///
/// Note: The pointer returned by this method may be invalidated after a
/// subsequent write operation.
- const void* getData() const { return (&data_[0]); }
+ const void* getData() const { return (buffer_); }
/// \brief Return the length of data written in the buffer.
- size_t getLength() const { return (data_.size()); }
+ size_t getLength() const { return (size_); }
/// \brief Return the value of the buffer at the specified position.
///
/// \c pos must specify the valid position of the buffer; otherwise an
/// exception class of \c InvalidBufferPosition will be thrown.
///
/// \param pos The position in the buffer to be returned.
- const uint8_t& operator[](size_t pos) const
+ uint8_t operator[](size_t pos) const
{
- if (pos >= data_.size()) {
+ if (pos >= size_) {
isc_throw(InvalidBufferPosition, "read at invalid position");
}
- return (data_[pos]);
+ return (buffer_[pos]);
}
//@}
@@ -330,7 +375,10 @@ public:
/// This method is provided as a shortcut to make a hole in the buffer
/// that is to be filled in later, e.g, by \ref writeUint16At().
/// \param len The length of the gap to be inserted in bytes.
- void skip(size_t len) { data_.insert(data_.end(), len, 0); }
+ void skip(size_t len) {
+ ensureAllocated(size_ + len);
+ size_ += len;
+ }
/// \brief Trim the specified length of data from the end of the buffer.
///
@@ -341,20 +389,23 @@ public:
/// \param len The length of data that should be trimmed.
void trim(size_t len)
{
- if (len > data_.size()) {
+ if (len > size_) {
isc_throw(OutOfRange, "trimming too large from output buffer");
}
- data_.resize(data_.size() - len);
+ size_ -= len;
}
/// \brief Clear buffer content.
///
/// This method can be used to re-initialize and reuse the buffer without
/// constructing a new one.
- void clear() { data_.clear(); }
+ void clear() { size_ = 0; }
/// \brief Write an unsigned 8-bit integer into the buffer.
///
/// \param data The 8-bit integer to be written into the buffer.
- void writeUint8(uint8_t data) { data_.push_back(data); }
+ void writeUint8(uint8_t data) {
+ ensureAllocated(size_ + 1);
+ buffer_[size_ ++] = data;
+ }
/// \brief Write an unsigned 8-bit integer into the buffer.
///
@@ -365,10 +416,10 @@ public:
/// \param data The 8-bit integer to be written into the buffer.
/// \param pos The position in the buffer to write the data.
void writeUint8At(uint8_t data, size_t pos) {
- if (pos + sizeof(data) > data_.size()) {
+ if (pos + sizeof(data) > size_) {
isc_throw(InvalidBufferPosition, "write at invalid position");
}
- data_[pos] = data;
+ buffer_[pos] = data;
}
/// \brief Write an unsigned 16-bit integer in host byte order into the
@@ -377,8 +428,9 @@ public:
/// \param data The 16-bit integer to be written into the buffer.
void writeUint16(uint16_t data)
{
- data_.push_back(static_cast<uint8_t>((data & 0xff00U) >> 8));
- data_.push_back(static_cast<uint8_t>(data & 0x00ffU));
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x00ffU);
}
/// \brief Write an unsigned 16-bit integer in host byte order at the
/// specified position of the buffer in network byte order.
@@ -393,12 +445,12 @@ public:
/// \param pos The beginning position in the buffer to write the data.
void writeUint16At(uint16_t data, size_t pos)
{
- if (pos + sizeof(data) > data_.size()) {
+ if (pos + sizeof(data) > size_) {
isc_throw(InvalidBufferPosition, "write at invalid position");
}
- data_[pos] = static_cast<uint8_t>((data & 0xff00U) >> 8);
- data_[pos + 1] = static_cast<uint8_t>(data & 0x00ffU);
+ buffer_[pos] = static_cast<uint8_t>((data & 0xff00U) >> 8);
+ buffer_[pos + 1] = static_cast<uint8_t>(data & 0x00ffU);
}
/// \brief Write an unsigned 32-bit integer in host byte order
/// into the buffer in network byte order.
@@ -406,10 +458,11 @@ public:
/// \param data The 32-bit integer to be written into the buffer.
void writeUint32(uint32_t data)
{
- data_.push_back(static_cast<uint8_t>((data & 0xff000000) >> 24));
- data_.push_back(static_cast<uint8_t>((data & 0x00ff0000) >> 16));
- data_.push_back(static_cast<uint8_t>((data & 0x0000ff00) >> 8));
- data_.push_back(static_cast<uint8_t>(data & 0x000000ff));
+ ensureAllocated(size_ + sizeof(data));
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0xff000000) >> 24);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x00ff0000) >> 16);
+ buffer_[size_ ++] = static_cast<uint8_t>((data & 0x0000ff00) >> 8);
+ buffer_[size_ ++] = static_cast<uint8_t>(data & 0x000000ff);
}
/// \brief Copy an arbitrary length of data into the buffer.
///
@@ -419,13 +472,38 @@ public:
/// \param len The length of the data in bytes.
void writeData(const void *data, size_t len)
{
- const uint8_t* cp = static_cast<const uint8_t*>(data);
- data_.insert(data_.end(), cp, cp + len);
+ ensureAllocated(size_ + len);
+ memcpy(buffer_ + size_, data, len);
+ size_ += len;
}
//@}
-
+
private:
- std::vector<uint8_t> data_;
+ // The actual data
+ uint8_t* buffer_;
+ // How many bytes are used
+ size_t size_;
+ // How many bytes do we have preallocated (eg. the capacity)
+ size_t allocated_;
+ // Make sure at last needed_size bytes are allocated in the buffer
+ void ensureAllocated(size_t needed_size) {
+ if (allocated_ < needed_size) {
+ // Guess some bigger size
+ size_t new_size = (allocated_ == 0) ? 1024 : allocated_;
+ while (new_size < needed_size) {
+ new_size *= 2;
+ }
+ // Allocate bigger space
+ uint8_t* new_buffer_(static_cast<uint8_t*>(realloc(buffer_,
+ new_size)));
+ if (new_buffer_ == NULL) {
+ // If it fails, the original block is left intact by it
+ throw std::bad_alloc();
+ }
+ buffer_ = new_buffer_;
+ allocated_ = new_size;
+ }
+ }
};
/// \brief Pointer-like types pointing to \c InputBuffer or \c OutputBuffer
diff --git a/src/lib/dns/edns.h b/src/lib/dns/edns.h
index f9a5758..ca3db47 100644
--- a/src/lib/dns/edns.h
+++ b/src/lib/dns/edns.h
@@ -213,7 +213,7 @@ public:
/// \param name The owner name of the OPT RR. This must be the root name.
/// \param rrclass The RR class of the OPT RR.
/// \param rrtype This must specify the OPT RR type.
- /// \param rrttl The TTL of the OPT RR.
+ /// \param ttl The TTL of the OPT RR.
/// \param rdata The RDATA of the OPT RR.
EDNS(const Name& name, const RRClass& rrclass, const RRType& rrtype,
const RRTTL& ttl, const rdata::Rdata& rdata);
@@ -418,7 +418,7 @@ private:
/// \param name The owner name of the OPT RR. This must be the root name.
/// \param rrclass The RR class of the OPT RR.
/// \param rrtype This must specify the OPT RR type.
-/// \param rrttl The TTL of the OPT RR.
+/// \param ttl The TTL of the OPT RR.
/// \param rdata The RDATA of the OPT RR.
/// \param extended_rcode A placeholder to store the topmost 8 bits of the
/// extended Rcode.
diff --git a/src/lib/dns/masterload.h b/src/lib/dns/masterload.h
index 76d6709..fe5c08f 100644
--- a/src/lib/dns/masterload.h
+++ b/src/lib/dns/masterload.h
@@ -110,7 +110,7 @@ typedef boost::function<void(RRsetPtr)> MasterLoadCallback;
/// but this is not even though it's valid per RFC1035:
/// \code example.com. IN 3600 A 192.0.2.1
/// \endcode
-/// - <TTL>, <RRCLASS>, and <RRTYPE> must be recognizable by the \c RRTTL,
+/// - "TTL", "RRCLASS", and "RRTYPE" must be recognizable by the \c RRTTL,
/// RRClass and RRType class implementations of this library. In particular,
/// as of this writing TTL must be a decimal number (a convenient extension
/// such as "1H" instead of 3600 cannot be used). Not all standard RR
@@ -213,7 +213,7 @@ typedef boost::function<void(RRsetPtr)> MasterLoadCallback;
/// \param filename A path to a master zone file to be loaded.
/// \param origin The origin name of the zone.
/// \param zone_class The RR class of the zone.
-/// \param callbck A callback functor or function that is to be called
+/// \param callback A callback functor or function that is to be called
/// for each RRset.
void masterLoad(const char* const filename, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback);
@@ -231,7 +231,7 @@ void masterLoad(const char* const filename, const Name& origin,
/// \param input An input stream object that is to emit zone's RRs.
/// \param origin The origin name of the zone.
/// \param zone_class The RR class of the zone.
-/// \param callbck A callback functor or function that is to be called for
+/// \param callback A callback functor or function that is to be called for
/// each RRset.
void masterLoad(std::istream& input, const Name& origin,
const RRClass& zone_class, MasterLoadCallback callback);
diff --git a/src/lib/dns/message.h b/src/lib/dns/message.h
index e65c09d..b46be9d 100644
--- a/src/lib/dns/message.h
+++ b/src/lib/dns/message.h
@@ -141,7 +141,7 @@ typedef SectionIterator<RRsetPtr> RRsetIterator;
/// - We may want to provide an "iterator" for all RRsets/RRs for convenience.
/// This will be for applications that do not care about performance much,
/// so the implementation can only be moderately efficient.
-/// - may want to provide a "find" method for a specified type
+/// - We may want to provide a "find" method for a specified type
/// of RR in the message.
class Message {
public:
@@ -155,8 +155,8 @@ public:
///
/// Only the defined constants are valid where a header flag is required
/// in this library (e.g., in \c Message::setHeaderFlag()).
- /// Since these are enum constants, however, invalid value could be passed
- /// via casting without an error at compilation time.
+ /// Since these are enum constants, however, an invalid value could be
+ /// passed via casting without an error at compilation time.
/// It is generally the callee's responsibility to check and reject invalid
/// values.
/// Of course, applications shouldn't pass invalid values even if the
@@ -168,7 +168,7 @@ public:
/// specified flag in the second 16 bits of the DNS Header section
/// in order to make the internal implementation simpler.
/// For example, \c HEADERFLAG_QR is defined to be 0x8000 as the QR
- /// bit is the most significant bit of the 2nd 16 bits of the header.
+ /// bit is the most significant bit of the second 16 bits of the header.
/// However, applications should not assume this coincidence and
/// must solely use the enum representations.
/// Any usage based on the assumption of the underlying values is invalid
@@ -199,8 +199,8 @@ public:
HEADERFLAG_TC = 0x0200, ///< Truncation
HEADERFLAG_RD = 0x0100, ///< Recursion desired
HEADERFLAG_RA = 0x0080, ///< Recursion available
- HEADERFLAG_AD = 0x0020, ///< DNSSEC checking disabled (RFC4035)
- HEADERFLAG_CD = 0x0010 ///< Authentic %data (RFC4035)
+ HEADERFLAG_AD = 0x0020, ///< Authentic %data (RFC4035)
+ HEADERFLAG_CD = 0x0010 ///< DNSSEC checking disabled (RFC4035)
};
/// \brief Constants to specify sections of a DNS message.
diff --git a/src/lib/dns/python/edns_python.cc b/src/lib/dns/python/edns_python.cc
index e54dba0..82db8d8 100644
--- a/src/lib/dns/python/edns_python.cc
+++ b/src/lib/dns/python/edns_python.cc
@@ -297,12 +297,15 @@ EDNS_getUDPSize(const s_EDNS* const self) {
PyObject*
EDNS_setUDPSize(s_EDNS* self, PyObject* args) {
- unsigned int size;
- if (!PyArg_ParseTuple(args, "I", &size)) {
+ long size;
+ if (!PyArg_ParseTuple(args, "l", &size)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "No valid type in set_udp_size argument");
return (NULL);
}
- if (size > 65535) {
- PyErr_SetString(PyExc_OverflowError,
+ if (size < 0 || size > 0xffff) {
+ PyErr_SetString(PyExc_ValueError,
"UDP size is not an unsigned 16-bit integer");
return (NULL);
}
diff --git a/src/lib/dns/python/message_python.cc b/src/lib/dns/python/message_python.cc
index e19547e..97bc28d 100644
--- a/src/lib/dns/python/message_python.cc
+++ b/src/lib/dns/python/message_python.cc
@@ -226,9 +226,9 @@ static PyTypeObject message_type = {
static int
Message_init(s_Message* self, PyObject* args) {
- unsigned int i;
-
- if (PyArg_ParseTuple(args, "I", &i)) {
+ int i;
+
+ if (PyArg_ParseTuple(args, "i", &i)) {
PyErr_Clear();
if (i == Message::PARSE) {
self->message = new Message(Message::PARSE);
@@ -274,17 +274,17 @@ Message_getHeaderFlag(s_Message* self, PyObject* args) {
static PyObject*
Message_setHeaderFlag(s_Message* self, PyObject* args) {
- int messageflag;
+ long messageflag;
PyObject *on = Py_True;
- if (!PyArg_ParseTuple(args, "i|O!", &messageflag, &PyBool_Type, &on)) {
+ if (!PyArg_ParseTuple(args, "l|O!", &messageflag, &PyBool_Type, &on)) {
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"no valid type in set_header_flag argument");
return (NULL);
}
- if (messageflag < 0) {
- PyErr_SetString(PyExc_TypeError, "invalid Message header flag");
+ if (messageflag < 0 || messageflag > 0xffff) {
+ PyErr_SetString(PyExc_ValueError, "Message header flag out of range");
return (NULL);
}
@@ -310,10 +310,19 @@ Message_getQid(s_Message* self) {
static PyObject*
Message_setQid(s_Message* self, PyObject* args) {
- uint16_t id;
- if (!PyArg_ParseTuple(args, "H", &id)) {
+ long id;
+ if (!PyArg_ParseTuple(args, "l", &id)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "no valid type in set_qid argument");
return (NULL);
}
+ if (id < 0 || id > 0xffff) {
+ PyErr_SetString(PyExc_ValueError,
+ "Message id out of range");
+ return (NULL);
+ }
+
try {
self->message->setQid(id);
Py_RETURN_NONE;
@@ -565,9 +574,9 @@ Message_addQuestion(s_Message* self, PyObject* args) {
static PyObject*
Message_addRRset(s_Message* self, PyObject* args) {
PyObject *sign = Py_False;
- unsigned int section;
+ int section;
s_RRset* rrset;
- if (!PyArg_ParseTuple(args, "IO!|O!", §ion, &rrset_type, &rrset,
+ if (!PyArg_ParseTuple(args, "iO!|O!", §ion, &rrset_type, &rrset,
&PyBool_Type, &sign)) {
return (NULL);
}
@@ -591,8 +600,8 @@ Message_addRRset(s_Message* self, PyObject* args) {
static PyObject*
Message_clear(s_Message* self, PyObject* args) {
- unsigned int i;
- if (PyArg_ParseTuple(args, "I", &i)) {
+ int i;
+ if (PyArg_ParseTuple(args, "i", &i)) {
PyErr_Clear();
if (i == Message::PARSE) {
self->message->clear(Message::PARSE);
diff --git a/src/lib/dns/python/messagerenderer_python.cc b/src/lib/dns/python/messagerenderer_python.cc
index a00d8d4..f12001d 100644
--- a/src/lib/dns/python/messagerenderer_python.cc
+++ b/src/lib/dns/python/messagerenderer_python.cc
@@ -178,8 +178,16 @@ static PyObject*
MessageRenderer_setLengthLimit(s_MessageRenderer* self,
PyObject* args)
{
- unsigned int lengthlimit;
- if (!PyArg_ParseTuple(args, "I", &lengthlimit)) {
+ long lengthlimit;
+ if (!PyArg_ParseTuple(args, "l", &lengthlimit)) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "No valid type in set_length_limit argument");
+ return (NULL);
+ }
+ if (lengthlimit < 0) {
+ PyErr_SetString(PyExc_ValueError,
+ "MessageRenderer length limit out of range");
return (NULL);
}
self->messagerenderer->setLengthLimit(lengthlimit);
@@ -190,8 +198,8 @@ static PyObject*
MessageRenderer_setCompressMode(s_MessageRenderer* self,
PyObject* args)
{
- unsigned int mode;
- if (!PyArg_ParseTuple(args, "I", &mode)) {
+ int mode;
+ if (!PyArg_ParseTuple(args, "i", &mode)) {
return (NULL);
}
diff --git a/src/lib/dns/python/name_python.cc b/src/lib/dns/python/name_python.cc
index 3d7a196..7d8913a 100644
--- a/src/lib/dns/python/name_python.cc
+++ b/src/lib/dns/python/name_python.cc
@@ -321,14 +321,20 @@ Name_init(s_Name* self, PyObject* args) {
PyObject* bytes_obj;
const char* bytes;
Py_ssize_t len;
- unsigned int position = 0;
+ long position = 0;
// It was not a string (see comment above), so try bytes, and
// create with buffer object
- if (PyArg_ParseTuple(args, "O|IO!", &bytes_obj, &position,
+ if (PyArg_ParseTuple(args, "O|lO!", &bytes_obj, &position,
&PyBool_Type, &downcase) &&
PyObject_AsCharBuffer(bytes_obj, &bytes, &len) != -1) {
try {
+ if (position < 0) {
+ // Throw IndexError here since name index should be unsigned
+ PyErr_SetString(PyExc_IndexError,
+ "Name index shouldn't be negative");
+ return (-1);
+ }
InputBuffer buffer(bytes, len);
buffer.setPosition(position);
@@ -363,10 +369,17 @@ Name_destroy(s_Name* self) {
static PyObject*
Name_at(s_Name* self, PyObject* args) {
- unsigned int pos;
- if (!PyArg_ParseTuple(args, "I", &pos)) {
+ int pos;
+ if (!PyArg_ParseTuple(args, "i", &pos)) {
+ return (NULL);
+ }
+ if (pos < 0) {
+ // Throw IndexError here since name index should be unsigned
+ PyErr_SetString(PyExc_IndexError,
+ "name index shouldn't be negative");
return (NULL);
}
+
try {
return (Py_BuildValue("I", self->name->at(pos)));
} catch (const isc::OutOfRange&) {
@@ -405,10 +418,10 @@ static PyObject*
Name_toWire(s_Name* self, PyObject* args) {
PyObject* bytes;
s_MessageRenderer* mr;
-
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(Name::MAX_WIRE);
self->name->toWire(buffer);
PyObject* name_bytes = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
@@ -457,12 +470,18 @@ Name_equals(s_Name* self, PyObject* args) {
Py_RETURN_FALSE;
}
-static PyObject*
+static PyObject*
Name_split(s_Name* self, PyObject* args) {
- unsigned int first, n;
+ int first, n;
s_Name* ret = NULL;
-
- if (PyArg_ParseTuple(args, "II", &first, &n)) {
+
+ if (PyArg_ParseTuple(args, "ii", &first, &n)) {
+ if (first < 0 || n < 0) {
+ // Throw IndexError here since name index should be unsigned
+ PyErr_SetString(PyExc_IndexError,
+ "name index shouldn't be negative");
+ return (NULL);
+ }
ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
ret->name = NULL;
@@ -477,7 +496,14 @@ Name_split(s_Name* self, PyObject* args) {
return (NULL);
}
}
- } else if (PyArg_ParseTuple(args, "I", &n)) {
+ } else if (PyArg_ParseTuple(args, "i", &n)) {
+ PyErr_Clear();
+ if (n < 0) {
+ // Throw IndexError here since name index should be unsigned
+ PyErr_SetString(PyExc_IndexError,
+ "name index shouldn't be negative");
+ return (NULL);
+ }
ret = PyObject_New(s_Name, &name_type);
if (ret != NULL) {
ret->name = NULL;
@@ -493,6 +519,10 @@ Name_split(s_Name* self, PyObject* args) {
}
}
}
+
+ PyErr_Clear();
+ PyErr_SetString(PyExc_TypeError,
+ "No valid type in split argument");
return (ret);
}
#include <iostream>
@@ -502,7 +532,7 @@ Name_split(s_Name* self, PyObject* args) {
// It is translated to a function that gets 3 arguments, an object,
// an object to compare to, and an operator.
//
-static PyObject*
+static PyObject*
Name_richcmp(s_Name* self, s_Name* other, int op) {
bool c;
diff --git a/src/lib/dns/python/question_python.cc b/src/lib/dns/python/question_python.cc
index a039284..2889350 100644
--- a/src/lib/dns/python/question_python.cc
+++ b/src/lib/dns/python/question_python.cc
@@ -169,7 +169,7 @@ Question_init(s_Question* self, PyObject* args) {
}
self->question = QuestionPtr();
-
+
PyErr_Clear();
PyErr_SetString(PyExc_TypeError,
"no valid type in constructor argument");
diff --git a/src/lib/dns/python/rcode_python.cc b/src/lib/dns/python/rcode_python.cc
index fce8eef..b80a93c 100644
--- a/src/lib/dns/python/rcode_python.cc
+++ b/src/lib/dns/python/rcode_python.cc
@@ -171,18 +171,18 @@ PyTypeObject rcode_type = {
int
Rcode_init(s_Rcode* const self, PyObject* args) {
- int code = 0;
+ long code = 0;
int ext_code = 0;
- if (PyArg_ParseTuple(args, "i", &code)) {
+ if (PyArg_ParseTuple(args, "l", &code)) {
if (code < 0 || code > 0xffff) {
- PyErr_SetString(PyExc_OverflowError, "Rcode out of range");
+ PyErr_SetString(PyExc_ValueError, "Rcode out of range");
return (-1);
}
ext_code = -1;
- } else if (PyArg_ParseTuple(args, "ii", &code, &ext_code)) {
+ } else if (PyArg_ParseTuple(args, "li", &code, &ext_code)) {
if (code < 0 || code > 0xff || ext_code < 0 || ext_code > 0xff) {
- PyErr_SetString(PyExc_OverflowError, "Rcode out of range");
+ PyErr_SetString(PyExc_ValueError, "Rcode out of range");
return (-1);
}
} else {
diff --git a/src/lib/dns/python/rrclass_python.cc b/src/lib/dns/python/rrclass_python.cc
index 3cfed4c..ca20e68 100644
--- a/src/lib/dns/python/rrclass_python.cc
+++ b/src/lib/dns/python/rrclass_python.cc
@@ -152,7 +152,7 @@ static PyTypeObject rrclass_type = {
static int
RRClass_init(s_RRClass* self, PyObject* args) {
const char* s;
- unsigned int i;
+ long i;
PyObject* bytes = NULL;
// The constructor argument can be a string ("IN"), an integer (1),
// or a sequence of numbers between 0 and 65535 (wire code)
@@ -165,10 +165,11 @@ RRClass_init(s_RRClass* self, PyObject* args) {
if (PyArg_ParseTuple(args, "s", &s)) {
self->rrclass = new RRClass(s);
return (0);
- } else if (PyArg_ParseTuple(args, "I", &i)) {
- PyErr_Clear();
- if (i > 65535) {
- PyErr_SetString(po_InvalidRRClass, "RR class number too high");
+ } else if (PyArg_ParseTuple(args, "l", &i)) {
+ if (i < 0 || i > 0xffff) {
+ PyErr_Clear();
+ PyErr_SetString(PyExc_ValueError,
+ "RR class number out of range");
return (-1);
}
self->rrclass = new RRClass(i);
diff --git a/src/lib/dns/python/rrttl_python.cc b/src/lib/dns/python/rrttl_python.cc
index e7391d0..696e1a0 100644
--- a/src/lib/dns/python/rrttl_python.cc
+++ b/src/lib/dns/python/rrttl_python.cc
@@ -145,7 +145,7 @@ static PyTypeObject rrttl_type = {
static int
RRTTL_init(s_RRTTL* self, PyObject* args) {
const char* s;
- unsigned long i;
+ long long i;
PyObject* bytes = NULL;
// The constructor argument can be a string ("1234"), an integer (1),
// or a sequence of numbers between 0 and 255 (wire code)
@@ -158,8 +158,12 @@ RRTTL_init(s_RRTTL* self, PyObject* args) {
if (PyArg_ParseTuple(args, "s", &s)) {
self->rrttl = new RRTTL(s);
return (0);
- } else if (PyArg_ParseTuple(args, "I", &i)) {
+ } else if (PyArg_ParseTuple(args, "L", &i)) {
PyErr_Clear();
+ if (i < 0 || i > 0xffffffff) {
+ PyErr_SetString(PyExc_ValueError, "RR TTL number out of range");
+ return (-1);
+ }
self->rrttl = new RRTTL(i);
return (0);
} else if (PyArg_ParseTuple(args, "O", &bytes) &&
diff --git a/src/lib/dns/python/rrtype_python.cc b/src/lib/dns/python/rrtype_python.cc
index 012f251..4fc0284 100644
--- a/src/lib/dns/python/rrtype_python.cc
+++ b/src/lib/dns/python/rrtype_python.cc
@@ -182,7 +182,7 @@ static PyTypeObject rrtype_type = {
static int
RRType_init(s_RRType* self, PyObject* args) {
const char* s;
- unsigned int i;
+ long i;
PyObject* bytes = NULL;
// The constructor argument can be a string ("A"), an integer (1),
// or a sequence of numbers between 0 and 65535 (wire code)
@@ -195,10 +195,10 @@ RRType_init(s_RRType* self, PyObject* args) {
if (PyArg_ParseTuple(args, "s", &s)) {
self->rrtype = new RRType(s);
return (0);
- } else if (PyArg_ParseTuple(args, "I", &i)) {
+ } else if (PyArg_ParseTuple(args, "l", &i)) {
PyErr_Clear();
- if (i > 65535) {
- PyErr_SetString(po_InvalidRRType, "RR Type number too high");
+ if (i < 0 || i > 0xffff) {
+ PyErr_SetString(PyExc_ValueError, "RR Type number out of range");
return (-1);
}
self->rrtype = new RRType(i);
@@ -259,10 +259,10 @@ static PyObject*
RRType_toWire(s_RRType* self, PyObject* args) {
PyObject* bytes;
s_MessageRenderer* mr;
-
+
if (PyArg_ParseTuple(args, "O", &bytes) && PySequence_Check(bytes)) {
PyObject* bytes_o = bytes;
-
+
OutputBuffer buffer(2);
self->rrtype->toWire(buffer);
PyObject* n = PyBytes_FromStringAndSize(static_cast<const char*>(buffer.getData()), buffer.getLength());
diff --git a/src/lib/dns/python/tests/edns_python_test.py b/src/lib/dns/python/tests/edns_python_test.py
index 3f03c90..b249213 100644
--- a/src/lib/dns/python/tests/edns_python_test.py
+++ b/src/lib/dns/python/tests/edns_python_test.py
@@ -62,17 +62,16 @@ class EDNSTest(unittest.TestCase):
edns = EDNS()
edns.set_udp_size(511)
self.assertEqual(511, edns.get_udp_size())
- edns.set_udp_size(0)
- self.assertEqual(0, edns.get_udp_size())
- edns.set_udp_size(65535)
- self.assertEqual(65535, edns.get_udp_size())
-
self.assertRaises(TypeError, edns.set_udp_size, "wrong")
# Range check. We need to do this at the binding level, so we need
# explicit tests for it.
- self.assertRaises(OverflowError, edns.set_udp_size, 65536)
- self.assertRaises(OverflowError, edns.set_udp_size, -1)
+ edns.set_udp_size(0)
+ self.assertEqual(0, edns.get_udp_size())
+ edns.set_udp_size(65535)
+ self.assertEqual(65535, edns.get_udp_size())
+ self.assertRaises(ValueError, edns.set_udp_size, 0x10000)
+ self.assertRaises(ValueError, edns.set_udp_size, -1)
def test_get_version(self):
self.assertEqual(EDNS.SUPPORTED_VERSION, EDNS().get_version())
diff --git a/src/lib/dns/python/tests/message_python_test.py b/src/lib/dns/python/tests/message_python_test.py
index 6ef42d4..bccc596 100644
--- a/src/lib/dns/python/tests/message_python_test.py
+++ b/src/lib/dns/python/tests/message_python_test.py
@@ -80,8 +80,10 @@ class MessageTest(unittest.TestCase):
"2001:db8::134"))
self.bogus_section = Message.SECTION_ADDITIONAL + 1
+ self.bogus_below_section = Message.SECTION_QUESTION - 1
def test_init(self):
+ self.assertRaises(TypeError, Message, -1)
self.assertRaises(TypeError, Message, 3)
self.assertRaises(TypeError, Message, "wrong")
@@ -109,22 +111,28 @@ class MessageTest(unittest.TestCase):
self.assertRaises(InvalidParameter, self.r.set_header_flag, 0)
self.assertRaises(InvalidParameter, self.r.set_header_flag, 0x7000)
self.assertRaises(InvalidParameter, self.r.set_header_flag, 0x0800)
- self.assertRaises(InvalidParameter, self.r.set_header_flag, 0x10000)
- self.assertRaises(TypeError, self.r.set_header_flag, 0x80000000)
- # this would cause overflow and result in a "valid" flag
- self.assertRaises(TypeError, self.r.set_header_flag,
- Message.HEADERFLAG_AA | 0x100000000)
- self.assertRaises(TypeError, self.r.set_header_flag, -1)
-
self.assertRaises(InvalidMessageOperation,
self.p.set_header_flag, Message.HEADERFLAG_AA)
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertRaises(ValueError, self.r.set_header_flag, 0x10000)
+ self.assertRaises(ValueError, self.r.set_header_flag, -1)
+
def test_set_qid(self):
self.assertRaises(TypeError, self.r.set_qid, "wrong")
self.assertRaises(InvalidMessageOperation,
self.p.set_qid, 123)
self.r.set_qid(1234)
self.assertEqual(1234, self.r.get_qid())
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.r.set_qid(0)
+ self.assertEqual(0, self.r.get_qid())
+ self.r.set_qid(0xffff)
+ self.assertEqual(0xffff, self.r.get_qid())
+ self.assertRaises(ValueError, self.r.set_qid, -1)
+ self.assertRaises(ValueError, self.r.set_qid, 0x10000)
def test_set_rcode(self):
self.assertRaises(TypeError, self.r.set_rcode, "wrong")
@@ -135,7 +143,7 @@ class MessageTest(unittest.TestCase):
self.assertRaises(InvalidMessageOperation,
self.p.set_rcode, rcode)
-
+
self.assertRaises(InvalidMessageOperation, self.p.get_rcode)
def test_set_opcode(self):
@@ -197,7 +205,7 @@ class MessageTest(unittest.TestCase):
self.assertRaises(InvalidMessageOperation, self.p.add_rrset,
Message.SECTION_ANSWER, self.rrset_a)
-
+
self.assertFalse(compare_rrset_list(section_rrset, self.r.get_section(Message.SECTION_ANSWER)))
self.assertEqual(0, self.r.get_rr_count(Message.SECTION_ANSWER))
self.r.add_rrset(Message.SECTION_ANSWER, self.rrset_a)
@@ -239,6 +247,8 @@ class MessageTest(unittest.TestCase):
Message.SECTION_ANSWER, self.rrset_a)
self.assertRaises(OverflowError, self.r.add_rrset,
self.bogus_section, self.rrset_a)
+ self.assertRaises(OverflowError, self.r.add_rrset,
+ self.bogus_below_section, self.rrset_a)
def test_clear(self):
self.assertEqual(None, self.r.clear(Message.PARSE))
diff --git a/src/lib/dns/python/tests/messagerenderer_python_test.py b/src/lib/dns/python/tests/messagerenderer_python_test.py
index 544ad23..5362496 100644
--- a/src/lib/dns/python/tests/messagerenderer_python_test.py
+++ b/src/lib/dns/python/tests/messagerenderer_python_test.py
@@ -98,6 +98,11 @@ class MessageRendererTest(unittest.TestCase):
renderer.set_length_limit(1024)
self.assertEqual(1024, renderer.get_length_limit())
self.assertRaises(TypeError, renderer.set_length_limit, "wrong")
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ renderer.set_length_limit(0)
+ self.assertEqual(0, renderer.get_length_limit())
+ self.assertRaises(ValueError, renderer.set_length_limit, -1)
def test_messagerenderer_set_compress_mode(self):
renderer = MessageRenderer()
diff --git a/src/lib/dns/python/tests/name_python_test.py b/src/lib/dns/python/tests/name_python_test.py
index 81060e9..b8e625a 100644
--- a/src/lib/dns/python/tests/name_python_test.py
+++ b/src/lib/dns/python/tests/name_python_test.py
@@ -95,11 +95,14 @@ class NameTest(unittest.TestCase):
b = bytearray()
b += b'\x07example'*32 + b'\x03com\x00'
self.assertRaises(DNSMessageFORMERR, Name, b, 0)
+ self.assertRaises(IndexError, Name, b, -1)
def test_at(self):
self.assertEqual(7, self.name1.at(0))
self.assertEqual(101, self.name1.at(1))
self.assertRaises(IndexError, self.name1.at, 100)
+ self.assertRaises(IndexError, self.name1.at, 0x10000)
+ self.assertRaises(IndexError, self.name1.at, -1)
self.assertRaises(TypeError, self.name1.at, "wrong")
def test_get_length(self):
@@ -151,14 +154,25 @@ class NameTest(unittest.TestCase):
self.assertEqual("completely.different.", s.to_text())
self.assertRaises(TypeError, self.name1.split, "wrong", 1)
self.assertRaises(TypeError, self.name1.split, 1, "wrong")
- self.assertRaises(IndexError, self.name1.split, 123, 1)
- self.assertRaises(IndexError, self.name1.split, 1, 123)
s = self.name1.split(1)
self.assertEqual("com.", s.to_text())
+
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertRaises(IndexError, self.name1.split, 123, 1)
+ self.assertRaises(IndexError, self.name1.split, 1, 123)
+ self.assertRaises(IndexError, self.name1.split, 0x10000, 5)
+ self.assertRaises(IndexError, self.name1.split, -1, -1)
+ self.assertRaises(IndexError, self.name1.split, 0, -1)
+ self.assertRaises(IndexError, self.name1.split, -1, 0x10000)
+
s = self.name1.split(0)
self.assertEqual("example.com.", s.to_text())
self.assertRaises(IndexError, self.name1.split, 123)
+ self.assertRaises(IndexError, self.name1.split, 0x10000)
+ self.assertRaises(IndexError, self.name1.split, -123)
+ self.assertRaises(TypeError, self.name1.split, -1)
def test_reverse(self):
self.assertEqual("com.example.", self.name1.reverse().to_text())
@@ -169,7 +183,6 @@ class NameTest(unittest.TestCase):
self.assertEqual("example.com.example.com.", self.name1.concatenate(self.name1).to_text())
self.assertRaises(TypeError, self.name1.concatenate, "wrong")
self.assertRaises(TooLongName, self.name1.concatenate, Name("example."*31))
-
def test_downcase(self):
self.assertEqual("EXAMPLE.com.", self.name4.to_text())
diff --git a/src/lib/dns/python/tests/question_python_test.py b/src/lib/dns/python/tests/question_python_test.py
index 6c4db4b..69e3051 100644
--- a/src/lib/dns/python/tests/question_python_test.py
+++ b/src/lib/dns/python/tests/question_python_test.py
@@ -45,7 +45,7 @@ class QuestionTest(unittest.TestCase):
# tests below based on cpp unit tests
# also tests get_name, get_class and get_type
def test_from_wire(self):
-
+
q = question_from_wire("question_fromWire")
self.assertEqual(self.example_name1, q.get_name())
diff --git a/src/lib/dns/python/tests/rcode_python_test.py b/src/lib/dns/python/tests/rcode_python_test.py
index 3577f84..77fed3a 100644
--- a/src/lib/dns/python/tests/rcode_python_test.py
+++ b/src/lib/dns/python/tests/rcode_python_test.py
@@ -23,12 +23,8 @@ from pydnspp import *
class RcodeTest(unittest.TestCase):
def test_init(self):
self.assertRaises(TypeError, Rcode, "wrong")
- self.assertRaises(OverflowError, Rcode, 65536)
- self.assertEqual(Rcode(0).get_code(), 0)
-
- self.assertEqual(0, Rcode(0).get_code())
self.assertEqual(0xfff, Rcode(0xfff).get_code()) # possible max code
-
+
# should fail on attempt of construction with an out of range code
self.assertRaises(OverflowError, Rcode, 0x1000)
self.assertRaises(OverflowError, Rcode, 0xffff)
@@ -38,7 +34,18 @@ class RcodeTest(unittest.TestCase):
self.assertEqual(Rcode.BADVERS_CODE, Rcode(0, 1).get_code())
self.assertEqual(0xfff, Rcode(0xf, 0xff).get_code())
self.assertRaises(OverflowError, Rcode, 0x10, 0xff)
-
+
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertEqual(Rcode(0).get_code(), 0)
+ self.assertEqual(Rcode(4095).get_code(), 4095)
+ self.assertEqual(Rcode(0, 0).get_code(), 0)
+ self.assertEqual(Rcode(0, 0).get_extended_code(), 0)
+ self.assertEqual(Rcode(15, 255).get_code(), 4095)
+ self.assertRaises(ValueError, Rcode, 65536)
+ self.assertRaises(ValueError, Rcode, 0x10, 0x100)
+ self.assertRaises(ValueError, Rcode, 0x100, 0x10)
+
def test_constants(self):
self.assertEqual(Rcode.NOERROR_CODE, Rcode(0).get_code())
self.assertEqual(Rcode.FORMERR_CODE, Rcode(1).get_code())
diff --git a/src/lib/dns/python/tests/rrclass_python_test.py b/src/lib/dns/python/tests/rrclass_python_test.py
index b5e8b5e..38d8c8c 100644
--- a/src/lib/dns/python/tests/rrclass_python_test.py
+++ b/src/lib/dns/python/tests/rrclass_python_test.py
@@ -32,12 +32,17 @@ class RRClassTest(unittest.TestCase):
b = bytearray(1)
b[0] = 123
self.assertRaises(TypeError, RRClass, b)
- self.assertRaises(InvalidRRClass, RRClass, 65536)
self.assertEqual(self.c1, RRClass(1))
b = bytearray()
self.c1.to_wire(b)
self.assertEqual(self.c1, RRClass(b))
-
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertRaises(ValueError, RRClass, 65536)
+ self.assertRaises(TypeError, RRClass, -1)
+ self.assertEqual(RRClass(65535).get_code(), 65535)
+ self.assertEqual(RRClass(0).get_code(), 0)
+
def test_rrclass_to_text(self):
self.assertEqual("IN", self.c1.to_text())
self.assertEqual("IN", str(self.c1))
diff --git a/src/lib/dns/python/tests/rrttl_python_test.py b/src/lib/dns/python/tests/rrttl_python_test.py
index 095a0b1..aa4a34d 100644
--- a/src/lib/dns/python/tests/rrttl_python_test.py
+++ b/src/lib/dns/python/tests/rrttl_python_test.py
@@ -25,7 +25,7 @@ class RRTTLTest(unittest.TestCase):
def setUp(self):
self.t1 = RRTTL(1)
self.t2 = RRTTL(3600)
-
+
def test_init(self):
self.assertRaises(InvalidRRTTL, RRTTL, "wrong")
self.assertRaises(TypeError, RRTTL, Exception())
@@ -39,7 +39,13 @@ class RRTTLTest(unittest.TestCase):
b[2] = 0
b[3] = 15
self.assertEqual(15, RRTTL(b).get_value())
-
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertRaises(TypeError, RRTTL, -1)
+ self.assertRaises(ValueError, RRTTL, 4294967296)
+ self.assertEqual(0, RRTTL(0).get_value())
+ self.assertEqual(4294967295, RRTTL(4294967295).get_value())
+
def test_rrttl_to_text(self):
self.assertEqual("1", self.t1.to_text())
self.assertEqual("1", str(self.t1))
diff --git a/src/lib/dns/python/tests/rrtype_python_test.py b/src/lib/dns/python/tests/rrtype_python_test.py
index c35e1e7..7135426 100644
--- a/src/lib/dns/python/tests/rrtype_python_test.py
+++ b/src/lib/dns/python/tests/rrtype_python_test.py
@@ -22,7 +22,7 @@ import os
from pydnspp import *
class TestModuleSpec(unittest.TestCase):
-
+
rrtype_1 = RRType(1)
rrtype_0x80 = RRType(0x80);
rrtype_0x800 = RRType(0x800);
@@ -32,24 +32,28 @@ class TestModuleSpec(unittest.TestCase):
def test_init(self):
- self.assertRaises(InvalidRRType, RRType, 65537)
b = bytearray(b'\x00\x01')
self.assertEqual(RRType("A"), RRType(b))
b = bytearray(b'\x01')
self.assertRaises(IncompleteRRType, RRType, b)
self.assertRaises(TypeError, RRType, Exception)
-
+ # Range check. We need to do this at the binding level, so we need
+ # explicit tests for it.
+ self.assertRaises(ValueError, RRType, 65536)
+ self.assertRaises(TypeError, RRType, -1)
+ self.assertEqual("TYPE65535", RRType(65535).to_text());
+ self.assertEqual("TYPE0", RRType(0).to_text());
+
def test_init_from_text(self):
self.assertEqual("A", RRType("A").to_text())
self.assertEqual("NS", RRType("NS").to_text());
self.assertEqual("NS", str(RRType("NS")));
-
self.assertEqual("TYPE65535", RRType("TYPE65535").to_text());
-
+
self.assertEqual(53, RRType("TYPE00053").get_code());
self.assertRaises(InvalidRRType, RRType, "TYPE000053");
-
+
self.assertRaises(InvalidRRType, RRType, "TYPE");
self.assertRaises(InvalidRRType, RRType, "TYPE-1");
self.assertRaises(InvalidRRType, RRType, "TYPExxx");
diff --git a/src/lib/dns/question.h b/src/lib/dns/question.h
index 851de2c..520207b 100644
--- a/src/lib/dns/question.h
+++ b/src/lib/dns/question.h
@@ -54,13 +54,13 @@ typedef boost::shared_ptr<const Question> ConstQuestionPtr;
/// class.
/// This may look odd in that an "RRset" and "Question" are similar from the
/// protocol point of view: Both are used as a semantics unit of DNS messages;
-/// both share the same set of components, name, RR type and RR class.
+/// both share the same set of components (name, RR type and RR class).
///
/// In fact, BIND9 didn't introduce a separate data structure for Questions,
/// and use the same \c "rdataset" structure for both RRsets and Questions.
/// We could take the same approach, but chose to adopt the different design.
/// One reason for that is because a Question and an RRset are still
-/// different, and a Question might not be cleanly defined if (e.g.) it were
+/// different, and a Question might not be cleanly defined, e.g., if it were
/// a derived class of some "RRset-like" class.
/// For example, we couldn't give a reasonable semantics for \c %getTTL() or
/// \c %setTTL() methods for a Question, since it's not associated with the
@@ -74,14 +74,14 @@ typedef boost::shared_ptr<const Question> ConstQuestionPtr;
///
/// On the other hand, we do not expect a strong need for customizing the
/// \c Question class, unlike the RRset.
-/// Handling the Question section of a DNS message is relatively a
+/// Handling the "Question" section of a DNS message is relatively a
/// simple work comparing to RRset-involved operations, so a unified
/// straightforward implementation should suffice for any use cases
/// including performance sensitive ones.
///
-/// We may, however, still want to have customized version of Question
+/// We may, however, still want to have a customized version of Question
/// for, e.g, highly optimized behavior, and may revisit this design choice
-/// as we have more experiences with this implementation.
+/// as we have more experience with this implementation.
///
/// One disadvantage of defining RRsets and Questions as unrelated classes
/// is that we cannot handle them in a polymorphic way.
diff --git a/src/lib/dns/rdata.cc b/src/lib/dns/rdata.cc
index 17fee25..19b266a 100644
--- a/src/lib/dns/rdata.cc
+++ b/src/lib/dns/rdata.cc
@@ -66,7 +66,8 @@ createRdata(const RRType& rrtype, const RRClass& rrclass,
len);
if (buffer.getPosition() - old_pos != len) {
- isc_throw(InvalidRdataLength, "RDLENGTH mismatch");
+ isc_throw(InvalidRdataLength, "RDLENGTH mismatch: " <<
+ buffer.getPosition() - old_pos << " != " << len);
}
return (rdata);
diff --git a/src/lib/dns/rdata.h b/src/lib/dns/rdata.h
index 8eaedd3..0908c75 100644
--- a/src/lib/dns/rdata.h
+++ b/src/lib/dns/rdata.h
@@ -161,6 +161,7 @@ public:
///
/// \return A string representation of \c Rdata.
virtual std::string toText() const = 0;
+
/// \brief Render the \c Rdata in the wire format into a buffer.
///
/// This is a pure virtual method without the definition; the actual
@@ -169,6 +170,7 @@ public:
///
/// \param buffer An output buffer to store the wire data.
virtual void toWire(OutputBuffer& buffer) const = 0;
+
/// \brief Render the \c Rdata in the wire format into a
/// \c MessageRenderer object.
///
@@ -251,6 +253,7 @@ public:
/// \param rdata_string A string of textual representation of generic
/// RDATA.
explicit Generic(const std::string& rdata_string);
+
///
/// \brief Constructor from wire-format data.
///
@@ -273,6 +276,7 @@ public:
/// \c Rdata to parse.
/// \param rdata_len The length in buffer of the \c Rdata. In bytes.
Generic(InputBuffer& buffer, size_t rdata_len);
+
///
/// \brief The destructor.
virtual ~Generic();
@@ -284,6 +288,7 @@ public:
///
/// \param source A reference to a \c generic::Generic object to copy from.
Generic(const Generic& source);
+
///
/// \brief The assignment operator.
///
@@ -293,6 +298,7 @@ public:
/// \param source A reference to a \c generic::Generic object to copy from.
Generic& operator=(const Generic& source);
//@}
+
///
/// \name Converter methods
///
@@ -307,6 +313,7 @@ public:
///
/// \return A string representation of \c generic::Generic.
virtual std::string toText() const;
+
///
/// \brief Render the \c generic::Generic in the wire format into a buffer.
///
@@ -317,6 +324,7 @@ public:
///
/// \param buffer An output buffer to store the wire data.
virtual void toWire(OutputBuffer& buffer) const;
+
/// \brief Render the \c generic::Generic in the wire format into a
/// \c MessageRenderer object.
///
@@ -329,6 +337,7 @@ public:
/// output buffer in which the \c Generic object is to be stored.
virtual void toWire(MessageRenderer& renderer) const;
//@}
+
///
/// \name Comparison method
///
@@ -421,6 +430,7 @@ std::ostream& operator<<(std::ostream& os, const Generic& rdata);
/// object.
RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
const std::string& rdata_string);
+
/// \brief Create RDATA of a given pair of RR type and class from
/// wire-format data.
///
@@ -444,6 +454,7 @@ RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
/// object.
RdataPtr createRdata(const RRType& rrtype, const RRClass& rrclass,
InputBuffer& buffer, size_t len);
+
/// \brief Create RDATA of a given pair of RR type and class, copying
/// of another RDATA of same kind.
///
diff --git a/src/lib/dns/rdata/generic/rp_17.cc b/src/lib/dns/rdata/generic/rp_17.cc
new file mode 100644
index 0000000..5b39dc3
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rp_17.cc
@@ -0,0 +1,124 @@
+// 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 <string>
+#include <sstream>
+
+#include <dns/buffer.h>
+#include <dns/messagerenderer.h>
+#include <dns/name.h>
+#include <dns/rdata.h>
+#include <dns/rdataclass.h>
+
+using namespace std;
+using namespace isc::dns;
+
+// BEGIN_ISC_NAMESPACE
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief Constructor from string.
+///
+/// \c rp_str must be formatted as follows:
+/// \code <mailbox name> <text name>
+/// \endcode
+/// where both fields must represent a valid domain name.
+///
+/// \exception InvalidRdataText The number of RDATA fields (must be 2) is
+/// incorrect.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// given name is invalid.
+/// \exception std::bad_alloc Memory allocation for names fails.
+RP::RP(const std::string& rp_str) :
+ // We cannot construct both names in the initialization list due to the
+ // necessary text processing, so we have to initialize them with a dummy
+ // name and replace them later.
+ mailbox_(Name::ROOT_NAME()), text_(Name::ROOT_NAME())
+{
+ istringstream iss(rp_str);
+ string mailbox_str, text_str;
+ iss >> mailbox_str >> text_str;
+
+ // Validation: A valid RP RR must have exactly two fields.
+ if (iss.bad() || iss.fail()) {
+ isc_throw(InvalidRdataText, "Invalid RP text: " << rp_str);
+ }
+ if (!iss.eof()) {
+ isc_throw(InvalidRdataText, "Invalid RP text (redundant field): "
+ << rp_str);
+ }
+
+ mailbox_ = Name(mailbox_str);
+ text_ = Name(text_str);
+}
+
+/// \brief Constructor from wire-format data.
+///
+/// This constructor doesn't check the validity of the second parameter (rdata
+/// length) for parsing.
+/// If necessary, the caller will check consistency.
+///
+/// \exception std::bad_alloc Memory allocation for names fails.
+/// \exception Other The constructor of the \c Name class will throw if the
+/// names in the wire is invalid.
+RP::RP(InputBuffer& buffer, size_t) : mailbox_(buffer), text_(buffer) {
+}
+
+/// \brief Copy constructor.
+///
+/// \exception std::bad_alloc Memory allocation fails in copying internal
+/// member variables (this should be very rare).
+RP::RP(const RP& other) :
+ Rdata(), mailbox_(other.mailbox_), text_(other.text_)
+{}
+
+/// \brief Convert the \c RP to a string.
+///
+/// The output of this method is formatted as described in the "from string"
+/// constructor (\c RP(const std::string&))).
+///
+/// \exception std::bad_alloc Internal resource allocation fails.
+///
+/// \return A \c string object that represents the \c RP object.
+std::string
+RP::toText() const {
+ return (mailbox_.toText() + " " + text_.toText());
+}
+
+void
+RP::toWire(OutputBuffer& buffer) const {
+ mailbox_.toWire(buffer);
+ text_.toWire(buffer);
+}
+
+void
+RP::toWire(MessageRenderer& renderer) const {
+ // Type RP is not "well-known", and name compression must be disabled
+ // per RFC3597.
+ renderer.writeName(mailbox_, false);
+ renderer.writeName(text_, false);
+}
+
+int
+RP::compare(const Rdata& other) const {
+ const RP& other_rp = dynamic_cast<const RP&>(other);
+
+ const int cmp = compareNames(mailbox_, other_rp.mailbox_);
+ if (cmp != 0) {
+ return (cmp);
+ }
+ return (compareNames(text_, other_rp.text_));
+}
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
diff --git a/src/lib/dns/rdata/generic/rp_17.h b/src/lib/dns/rdata/generic/rp_17.h
new file mode 100644
index 0000000..a90a530
--- /dev/null
+++ b/src/lib/dns/rdata/generic/rp_17.h
@@ -0,0 +1,88 @@
+// 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.
+
+// BEGIN_HEADER_GUARD
+
+#include <string>
+
+#include <dns/name.h>
+#include <dns/rdata.h>
+
+// BEGIN_ISC_NAMESPACE
+
+// BEGIN_COMMON_DECLARATIONS
+// END_COMMON_DECLARATIONS
+
+// BEGIN_RDATA_NAMESPACE
+
+/// \brief \c rdata::generic::RP class represents the RP RDATA as defined in
+/// RFC1183.
+///
+/// This class implements the basic interfaces inherited from the abstract
+/// \c rdata::Rdata class, and provides trivial accessors specific to the
+/// RP RDATA.
+class RP : public Rdata {
+public:
+ // BEGIN_COMMON_MEMBERS
+ // END_COMMON_MEMBERS
+
+ /// We use the default copy constructor and assignment operator.
+
+ /// \brief Constructor from RDATA field parameters.
+ ///
+ /// The parameters are a straightforward mapping of %RP RDATA
+ /// fields as defined in RFC1183.
+ RP(const Name& mailbox, const Name& text) :
+ mailbox_(mailbox), text_(text)
+ {}
+
+ /// \brief Return the value of the mailbox field.
+ ///
+ /// This method normally does not throw an exception, but if resource
+ /// allocation for the returned \c Name object fails, a corresponding
+ /// standard exception will be thrown.
+ ///
+ /// \note
+ /// Unlike the case of some other RDATA classes (such as
+ /// \c NS::getNSName()), this method constructs a new \c Name object
+ /// and returns it, instead of returning a reference to a \c Name object
+ /// internally maintained in the class (which is a private member).
+ /// This is based on the observation that this method will be rarely used
+ /// and even when it's used it will not be in a performance context
+ /// (for example, a recursive resolver won't need this field in its
+ /// resolution process). By returning a new object we have flexibility of
+ /// changing the internal representation without the risk of changing
+ /// the interface or method property.
+ /// The same note applies to the \c getText() method.
+ Name getMailbox() const { return (mailbox_); }
+
+ /// \brief Return the value of the text field.
+ ///
+ /// This method normally does not throw an exception, but if resource
+ /// allocation for the returned \c Name object fails, a corresponding
+ /// standard exception will be thrown.
+ Name getText() const { return (text_); }
+
+private:
+ Name mailbox_;
+ Name text_;
+};
+
+// END_RDATA_NAMESPACE
+// END_ISC_NAMESPACE
+// END_HEADER_GUARD
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/dns/rdata/template.cc b/src/lib/dns/rdata/template.cc
index 0e0bf46..b737f60 100644
--- a/src/lib/dns/rdata/template.cc
+++ b/src/lib/dns/rdata/template.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// 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
diff --git a/src/lib/dns/rdata/template.h b/src/lib/dns/rdata/template.h
index e85a839..9e84cc3 100644
--- a/src/lib/dns/rdata/template.h
+++ b/src/lib/dns/rdata/template.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
+// 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
diff --git a/src/lib/dns/rrset.h b/src/lib/dns/rrset.h
index acb4224..926a58f 100644
--- a/src/lib/dns/rrset.h
+++ b/src/lib/dns/rrset.h
@@ -278,8 +278,6 @@ public:
/// name when possible in the context of zone dump. This is a future
/// TODO item.
///
- /// \param rrset A reference to a (derived class of) \c AbstractRRset object
- /// whose content is to be converted.
/// \return A string representation of the RRset.
virtual std::string toText() const = 0;
diff --git a/src/lib/dns/rrttl.h b/src/lib/dns/rrttl.h
index c80030e..695306a 100644
--- a/src/lib/dns/rrttl.h
+++ b/src/lib/dns/rrttl.h
@@ -118,7 +118,8 @@ public:
/// If resource allocation in rendering process fails, a corresponding
/// standard exception will be thrown.
///
- /// \param buffer An output buffer to store the wire data.
+ /// \param renderer DNS message rendering context that encapsulates the
+ /// output buffer in which the RRTTL is to be stored.
void toWire(MessageRenderer& renderer) const;
/// \brief Render the \c RRTTL in the wire format.
///
@@ -128,8 +129,7 @@ public:
/// If resource allocation in rendering process fails, a corresponding
/// standard exception will be thrown.
///
- /// \param renderer DNS message rendering context that encapsulates the
- /// output buffer in which the RRTTL is to be stored.
+ /// \param buffer An output buffer to store the wire data.
void toWire(OutputBuffer& buffer) const;
//@}
diff --git a/src/lib/dns/tests/Makefile.am b/src/lib/dns/tests/Makefile.am
index 246adb7..2bd7c87 100644
--- a/src/lib/dns/tests/Makefile.am
+++ b/src/lib/dns/tests/Makefile.am
@@ -39,6 +39,7 @@ run_unittests_SOURCES += rdata_nsec3_unittest.cc
run_unittests_SOURCES += rdata_nsecbitmap_unittest.cc
run_unittests_SOURCES += rdata_nsec3param_unittest.cc
run_unittests_SOURCES += rdata_rrsig_unittest.cc
+run_unittests_SOURCES += rdata_rp_unittest.cc
run_unittests_SOURCES += rdata_tsig_unittest.cc
run_unittests_SOURCES += rrset_unittest.cc rrsetlist_unittest.cc
run_unittests_SOURCES += question_unittest.cc
diff --git a/src/lib/dns/tests/buffer_unittest.cc b/src/lib/dns/tests/buffer_unittest.cc
index fb4c86c..0973912 100644
--- a/src/lib/dns/tests/buffer_unittest.cc
+++ b/src/lib/dns/tests/buffer_unittest.cc
@@ -130,7 +130,7 @@ TEST_F(BufferTest, outputBufferWriteat) {
const uint8_t* cp = static_cast<const uint8_t*>(obuffer.getData());
EXPECT_EQ(4, *(cp + 1));
- // overwrite 2nd and 3rd bytes again
+ // overwrite 2nd and 3rd bytes
obuffer.writeUint16At(data16, 1);
EXPECT_EQ(expected_size, obuffer.getLength()); // length shouldn't change
cp = static_cast<const uint8_t*>(obuffer.getData());
@@ -144,6 +144,10 @@ TEST_F(BufferTest, outputBufferWriteat) {
EXPECT_EQ(2, *(cp + 2));
EXPECT_EQ(3, *(cp + 3));
+ EXPECT_THROW(obuffer.writeUint8At(data16, 5),
+ isc::dns::InvalidBufferPosition);
+ EXPECT_THROW(obuffer.writeUint8At(data16, 4),
+ isc::dns::InvalidBufferPosition);
EXPECT_THROW(obuffer.writeUint16At(data16, 3),
isc::dns::InvalidBufferPosition);
EXPECT_THROW(obuffer.writeUint16At(data16, 4),
@@ -186,4 +190,53 @@ TEST_F(BufferTest, outputBufferClear) {
obuffer.clear();
EXPECT_EQ(0, obuffer.getLength());
}
+
+TEST_F(BufferTest, outputBufferCopy) {
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ OutputBuffer copy(obuffer);
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], copy[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], copy[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), copy.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputBufferAssign) {
+ OutputBuffer another(0);
+ another.clear();
+ obuffer.writeData(testdata, sizeof(testdata));
+
+ EXPECT_NO_THROW({
+ another = obuffer;
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ for (int i = 0; i < sizeof(testdata); i ++) {
+ EXPECT_EQ(testdata[i], another[i]);
+ if (i + 1 < sizeof(testdata)) {
+ obuffer.writeUint16At(0, i);
+ }
+ EXPECT_EQ(testdata[i], another[i]);
+ }
+ obuffer.clear();
+ ASSERT_EQ(sizeof(testdata), another.getLength());
+ });
+}
+
+TEST_F(BufferTest, outputBufferZeroSize) {
+ // Some OSes might return NULL on malloc for 0 size, so check it works
+ EXPECT_NO_THROW({
+ OutputBuffer first(0);
+ OutputBuffer copy(first);
+ OutputBuffer second(0);
+ second = first;
+ });
+}
+
}
diff --git a/src/lib/dns/tests/rdata_rp_unittest.cc b/src/lib/dns/tests/rdata_rp_unittest.cc
new file mode 100644
index 0000000..52285f1
--- /dev/null
+++ b/src/lib/dns/tests/rdata_rp_unittest.cc
@@ -0,0 +1,162 @@
+// 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 <string>
+
+#include <dns/buffer.h>
+#include <dns/exceptions.h>
+#include <dns/rdataclass.h>
+
+#include <gtest/gtest.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/tests/rdata_unittest.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+
+namespace {
+class Rdata_RP_Test : public RdataTest {
+protected:
+ Rdata_RP_Test() :
+ mailbox_name("root.example.com."),
+ text_name("rp-text.example.com."),
+ // this also serves as a test for "from text" constructor in a normal
+ // case.
+ rdata_rp("root.example.com. rp-text.example.com."),
+ obuffer(0), renderer(obuffer)
+ {}
+
+ const Name mailbox_name, text_name;
+ const generic::RP rdata_rp; // commonly used test RDATA
+ OutputBuffer obuffer;
+ MessageRenderer renderer;
+ vector<uint8_t> expected_wire;
+};
+
+TEST_F(Rdata_RP_Test, createFromText) {
+ EXPECT_EQ(mailbox_name, rdata_rp.getMailbox());
+ EXPECT_EQ(text_name, rdata_rp.getText());
+
+ // Invalid textual input cases follow:
+ // names are invalid
+ EXPECT_THROW(generic::RP("bad..name. rp-text.example.com"), EmptyLabel);
+ EXPECT_THROW(generic::RP("mailbox.example.com. bad..name"), EmptyLabel);
+
+ // missing field
+ EXPECT_THROW(generic::RP("mailbox.example.com."), InvalidRdataText);
+
+ // redundant field
+ EXPECT_THROW(generic::RP("mailbox.example.com. rp-text.example.com. "
+ "redundant.example."), InvalidRdataText);
+}
+
+TEST_F(Rdata_RP_Test, createFromWire) {
+ RdataPtr rdata(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire1.wire"));
+ EXPECT_EQ(mailbox_name, dynamic_cast<generic::RP&>(*rdata).getMailbox());
+ EXPECT_EQ(text_name, dynamic_cast<generic::RP&>(*rdata).getText());
+
+ // a similar test with names being compressed
+ rdata = rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire2.wire", 30);
+ EXPECT_EQ(mailbox_name, dynamic_cast<generic::RP&>(*rdata).getMailbox());
+ EXPECT_EQ(Name("rp-text.example.net"),
+ dynamic_cast<generic::RP&>(*rdata).getText());
+}
+
+TEST_F(Rdata_RP_Test, badFromWire) {
+ // RDLEN is too short
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire3.wire"),
+ InvalidRdataLength);
+
+ // RDLEN is too long
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire4.wire"),
+ InvalidRdataLength);
+
+ // bogus mailbox name
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire5.wire"),
+ DNSMessageFORMERR);
+
+ // bogus text name
+ EXPECT_THROW(rdataFactoryFromFile(RRType::RP(), RRClass::IN(),
+ "rdata_rp_fromWire6.wire"),
+ DNSMessageFORMERR);
+}
+
+TEST_F(Rdata_RP_Test, createFromParams) {
+ EXPECT_EQ(mailbox_name, generic::RP(mailbox_name, text_name).getMailbox());
+ EXPECT_EQ(text_name, generic::RP(mailbox_name, text_name).getText());
+}
+
+TEST_F(Rdata_RP_Test, toWireBuffer) {
+ // construct expected data
+ UnitTestUtil::readWireData("rdata_rp_toWire1.wire", expected_wire);
+
+ // construct actual data
+ rdata_rp.toWire(obuffer);
+
+ // then compare them
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ obuffer.getData(), obuffer.getLength(),
+ &expected_wire[0], expected_wire.size());
+}
+
+TEST_F(Rdata_RP_Test, toWireRenderer) {
+ // similar to toWireBuffer, but names in RDATA could be compressed due to
+ // preceding names. Actually they must not be compressed according to
+ // RFC3597, and this test checks that.
+
+ UnitTestUtil::readWireData("rdata_rp_toWire2.wire", expected_wire);
+
+ renderer.writeName(Name("a.example.com"));
+ renderer.writeName(Name("b.example.net"));
+ generic::RP(mailbox_name, Name("rp-text.example.net")).toWire(renderer);
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData,
+ renderer.getData(), renderer.getLength(),
+ &expected_wire[0], expected_wire.size());
+}
+
+TEST_F(Rdata_RP_Test, toText) {
+ // there's not much to test for this method. Only checking a simple case.
+ EXPECT_EQ("root.example.com. rp-text.example.com.", rdata_rp.toText());
+}
+
+TEST_F(Rdata_RP_Test, compare) {
+ // check reflexivity
+ EXPECT_EQ(0, rdata_rp.compare(rdata_rp));
+
+ // names must be compared in case-insensitive manner
+ EXPECT_EQ(0, rdata_rp.compare(generic::RP("ROOT.example.com. "
+ "rp-text.EXAMPLE.com.")));
+
+ // another RP whose mailbox name is larger than that of rdata_rp.
+ const generic::RP large1_rp("zzzz.example.com. rp-text.example.com.");
+ EXPECT_GT(0, rdata_rp.compare(large1_rp));
+ EXPECT_LT(0, large1_rp.compare(rdata_rp));
+
+ // yet another RP whose text name is larger than that of rdata_rp.
+ const generic::RP large2_rp("root.example.com. zzzzzzz.example.com.");
+ EXPECT_GT(0, rdata_rp.compare(large2_rp));
+ EXPECT_LT(0, large2_rp.compare(rdata_rp));
+
+ // comparison attempt between incompatible RR types should be rejected
+ EXPECT_THROW(rdata_rp.compare(*RdataTest::rdata_nomatch), bad_cast);
+}
+}
diff --git a/src/lib/dns/tests/testdata/Makefile.am b/src/lib/dns/tests/testdata/Makefile.am
index 1aaddb6..1dc1c00 100644
--- a/src/lib/dns/tests/testdata/Makefile.am
+++ b/src/lib/dns/tests/testdata/Makefile.am
@@ -16,6 +16,10 @@ BUILT_SOURCES += rdata_nsec3_fromWire10.wire rdata_nsec3_fromWire11.wire
BUILT_SOURCES += rdata_nsec3_fromWire12.wire rdata_nsec3_fromWire13.wire
BUILT_SOURCES += rdata_nsec3_fromWire14.wire rdata_nsec3_fromWire15.wire
BUILT_SOURCES += rdata_rrsig_fromWire2.wire
+BUILT_SOURCES += rdata_rp_fromWire1.wire rdata_rp_fromWire2.wire
+BUILT_SOURCES += rdata_rp_fromWire3.wire rdata_rp_fromWire4.wire
+BUILT_SOURCES += rdata_rp_fromWire5.wire rdata_rp_fromWire6.wire
+BUILT_SOURCES += rdata_rp_toWire1.wire rdata_rp_toWire2.wire
BUILT_SOURCES += rdata_soa_toWireUncompressed.wire
BUILT_SOURCES += rdata_txt_fromWire2.wire rdata_txt_fromWire3.wire
BUILT_SOURCES += rdata_txt_fromWire4.wire rdata_txt_fromWire5.wire
@@ -67,6 +71,10 @@ EXTRA_DIST += rdata_nsec3_fromWire12.spec rdata_nsec3_fromWire13.spec
EXTRA_DIST += rdata_nsec3_fromWire14.spec rdata_nsec3_fromWire15.spec
EXTRA_DIST += rdata_opt_fromWire rdata_rrsig_fromWire1
EXTRA_DIST += rdata_rrsig_fromWire2.spec
+EXTRA_DIST += rdata_rp_fromWire1.spec rdata_rp_fromWire2.spec
+EXTRA_DIST += rdata_rp_fromWire3.spec rdata_rp_fromWire4.spec
+EXTRA_DIST += rdata_rp_fromWire5.spec rdata_rp_fromWire6.spec
+EXTRA_DIST += rdata_rp_toWire1.spec rdata_rp_toWire2.spec
EXTRA_DIST += rdata_soa_fromWire rdata_soa_toWireUncompressed.spec
EXTRA_DIST += rdata_txt_fromWire1 rdata_txt_fromWire2.spec
EXTRA_DIST += rdata_txt_fromWire3.spec rdata_txt_fromWire4.spec
diff --git a/src/lib/dns/tests/testdata/gen-wiredata.py.in b/src/lib/dns/tests/testdata/gen-wiredata.py.in
index 645430c..72cbdaa 100755
--- a/src/lib/dns/tests/testdata/gen-wiredata.py.in
+++ b/src/lib/dns/tests/testdata/gen-wiredata.py.in
@@ -90,7 +90,7 @@ def encode_name(name, absolute=True):
for l in labels:
if len(l) > 4 and l[0:4] == 'ptr=':
# special meta-syntax for compression pointer
- wire += ' %04x' % (0xc000 | int(l[4:]))
+ wire += '%04x' % (0xc000 | int(l[4:]))
break
if absolute or len(l) > 0:
wire += '%02x' % len(l)
@@ -277,6 +277,34 @@ class TXT:
' ' if len(wirestring_list[i]) > 0 else '',
wirestring_list[i]))
+class RP:
+ '''Implements rendering RP RDATA in the wire format.
+ Configurable parameters are as follows:
+ - rdlen: 16-bit RDATA length. If omitted, the accurate value is auto
+ calculated and used; if negative, the RDLEN field will be omitted from
+ the output data.
+ - mailbox: The mailbox field.
+ - text: The text field.
+ All of these parameters have the default values and can be omitted.
+ '''
+ rdlen = None # auto-calculate
+ mailbox = 'root.example.com'
+ text = 'rp-text.example.com'
+ def dump(self, f):
+ mailbox_wire = encode_name(self.mailbox)
+ text_wire = encode_name(self.text)
+ if self.rdlen is None:
+ self.rdlen = (len(mailbox_wire) + len(text_wire)) / 2
+ else:
+ self.rdlen = int(self.rdlen)
+ if self.rdlen >= 0:
+ f.write('\n# RP RDATA (RDLEN=%d)\n' % self.rdlen)
+ f.write('%04x\n' % self.rdlen)
+ else:
+ f.write('\n# RP RDATA (RDLEN omitted)\n')
+ f.write('# MAILBOX=%s TEXT=%s\n' % (self.mailbox, self.text))
+ f.write('%s %s\n' % (mailbox_wire, text_wire))
+
class NSECBASE:
'''Implements rendering NSEC/NSEC3 type bitmaps commonly used for
these RRs. The NSEC and NSEC3 classes will be inherited from this
@@ -461,8 +489,9 @@ def get_config_param(section):
'header' : (DNSHeader, header_xtables),
'question' : (DNSQuestion, question_xtables),
'edns' : (EDNS, {}), 'soa' : (SOA, {}), 'txt' : (TXT, {}),
- 'rrsig' : (RRSIG, {}), 'nsec' : (NSEC, {}),
- 'nsec3' : (NSEC3, {}), 'tsig' : (TSIG, {}) }
+ 'rp' : (RP, {}), 'rrsig' : (RRSIG, {}),
+ 'nsec' : (NSEC, {}), 'nsec3' : (NSEC3, {}),
+ 'tsig' : (TSIG, {}) }
s = section
m = re.match('^([^:]+)/\d+$', section)
if m:
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec
new file mode 100644
index 0000000..edb9f34
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire1.spec
@@ -0,0 +1,6 @@
+#
+# A simplest form of RP: all default parameters
+#
+[custom]
+sections: rp
+[rp]
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec
new file mode 100644
index 0000000..57adb5a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire2.spec
@@ -0,0 +1,12 @@
+#
+# A simplest form of RP: names are compressed.
+#
+[custom]
+sections: name/1:name/2:rp
+[name/1]
+name: a.example.com
+[name/2]
+name: b.example.net
+[rp]
+mailbox: root.ptr=2
+text: rp-text.ptr=17
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec
new file mode 100644
index 0000000..a238b7e
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire3.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but RDLEN is too short.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: 38
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec
new file mode 100644
index 0000000..6f3abd1
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire4.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but RDLEN is too long.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: 40
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec
new file mode 100644
index 0000000..b8d5e29
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire5.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but mailbox name is broken.
+#
+[custom]
+sections: rp
+[rp]
+mailbox: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec
new file mode 100644
index 0000000..e9e79f3
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_fromWire6.spec
@@ -0,0 +1,7 @@
+#
+# RP-like RDATA but text name is broken.
+#
+[custom]
+sections: rp
+[rp]
+text: "01234567890123456789012345678901234567890123456789012345678901234"
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec b/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec
new file mode 100644
index 0000000..948bd1a
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire1.spec
@@ -0,0 +1,8 @@
+#
+# A simplest form of RP for toWire test: all default parameters except rdlen,
+# which is to be omitted.
+#
+[custom]
+sections: rp
+[rp]
+rdlen: -1
diff --git a/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec b/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec
new file mode 100644
index 0000000..09a7ddc
--- /dev/null
+++ b/src/lib/dns/tests/testdata/rdata_rp_toWire2.spec
@@ -0,0 +1,14 @@
+#
+# A simple form of RP: names could be compressed (but MUST NOT).
+# rdlen is omitted for the "to wire" test.
+#
+[custom]
+sections: name/1:name/2:rp
+[name/1]
+name: a.example.com
+[name/2]
+name: b.example.net
+[rp]
+rdlen: -1
+mailbox: root.example.com
+text: rp-text.example.net
diff --git a/src/lib/log/Makefile.am b/src/lib/log/Makefile.am
index 60b3f5b..d941b01 100644
--- a/src/lib/log/Makefile.am
+++ b/src/lib/log/Makefile.am
@@ -23,9 +23,10 @@ liblog_la_SOURCES += message_types.h
liblog_la_SOURCES += root_logger_name.cc root_logger_name.h
liblog_la_SOURCES += strutil.h strutil.cc
-EXTRA_DIST = README
-EXTRA_DIST += logger_impl_log4cxx.cc logger_impl_log4cxx.h
-EXTRA_DIST += xdebuglevel.cc xdebuglevel.h
+EXTRA_DIST = README
+EXTRA_DIST += messagedef.mes
+EXTRA_DIST += logger_impl_log4cxx.cc logger_impl_log4cxx.h
+EXTRA_DIST += xdebuglevel.cc xdebuglevel.h
# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
# B10_CXXFLAGS)
diff --git a/src/lib/log/README b/src/lib/log/README
index 072649e..ed11b5b 100644
--- a/src/lib/log/README
+++ b/src/lib/log/README
@@ -158,7 +158,7 @@ The symbols define the keys in the global message dictionary.
The namespace enclosing the symbols is set by the $NAMESPACE directive.
The "PREFIX_" part of the symbol name is the string defined in the $PREFIX
-the argument to the directive. So "$PREFIX MSG_" would prefix the identifer
+the argument to the directive. So "$PREFIX MSG_" would prefix the identifier
ABC with "MSG_" to give the symbol MSG_ABC. Similarly "$PREFIX E" would
prefix it with "E" to give the symbol EABC. If no $PREFIX is given, no
prefix appears (so the symbol in this example would be ABC).
@@ -330,7 +330,7 @@ When logging events, make a distinction between events related to the server
and events related to DNS messages received. Caution needs to be exercised
with the latter as, if the logging is enabled in the normal course of events,
such logging could be a denial of service vector. For example, suppose that
-the main authoritiative service logger were to log both zone loading and
+the main authoritative service logger were to log both zone loading and
unloading as INFO and a warning message if it received an invalid packet. An
attacker could make the INFO messages unusable by flooding the server with
malformed packets.
diff --git a/src/lib/log/dummylog.cc b/src/lib/log/dummylog.cc
index dda0578..5f025e1 100644
--- a/src/lib/log/dummylog.cc
+++ b/src/lib/log/dummylog.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
diff --git a/src/lib/log/dummylog.h b/src/lib/log/dummylog.h
index f20c032..ef5af13 100644
--- a/src/lib/log/dummylog.h
+++ b/src/lib/log/dummylog.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
@@ -34,11 +34,11 @@ extern std::string dprefix;
* \short Temporary interface to logging.
*
* This is a temporary function to do logging. It has wrong interface currently
- * and should be replaced by something else. It's main purpose now is to mark
+ * and should be replaced by something else. Its main purpose now is to mark
* places where logging should happen. When it is removed, compiler will do
* our work of finding the places.
*
- * The only thing it does is printing the dprogram prefix, message and
+ * The only thing it does is printing the program prefix, message and
* a newline if denabled is true.
*
* There are no tests for this function, since it is only temporary and
@@ -51,6 +51,7 @@ extern std::string dprefix;
*
* @param message The message to log. The real interface will probably have
* more parameters.
+ * \param error_flag TODO
*/
void dlog(const std::string& message, bool error_flag=false);
diff --git a/src/lib/log/filename.h b/src/lib/log/filename.h
index da9e560..e3cda16 100644
--- a/src/lib/log/filename.h
+++ b/src/lib/log/filename.h
@@ -131,7 +131,7 @@ public:
/// \param name Name to expand
///
/// \return Name expanded with stored name
- std::string useAsDefault(const std::string&) const;
+ std::string useAsDefault(const std::string& name) const;
private:
/// \brief Split Name into Components
diff --git a/src/lib/log/logger.h b/src/lib/log/logger.h
index 691eb73..88e88e2 100644
--- a/src/lib/log/logger.h
+++ b/src/lib/log/logger.h
@@ -73,7 +73,7 @@ public:
/// deletion of the underlying log4cxx data structures when the logger is
/// deleted. Setting it false for externally-declared loggers inhibits
/// their deletion; so at program exit the memory is not reclaimed during
- /// program rundown, only when the process is delected. Setting it true
+ /// program rundown, only when the process is selected. Setting it true
/// for loggers that will be deleted in the normal running of the program
/// enables their deletion - which causes no issues as the problem only
/// manifests itself during program rundown.
diff --git a/src/lib/log/logger_support.cc b/src/lib/log/logger_support.cc
index 1ac4481..f8bf075 100644
--- a/src/lib/log/logger_support.cc
+++ b/src/lib/log/logger_support.cc
@@ -21,7 +21,7 @@
/// appropriate).
/// b) Reads in the local message file is one has been supplied.
///
-/// These functions will be replaced once the code has bneen written to obtain
+/// These functions will be replaced once the code has been written to obtain
/// the logging parameters from the configuration database.
#include <algorithm>
diff --git a/src/lib/log/logger_support.h b/src/lib/log/logger_support.h
index 57d8383..6b5fdec 100644
--- a/src/lib/log/logger_support.h
+++ b/src/lib/log/logger_support.h
@@ -34,7 +34,7 @@ namespace log {
///
/// \param root Name of the root logger
/// \param severity Severity at which to log
-/// \param dbglevel Debug severiy (ignored if "severity" is not "DEBUG")
+/// \param dbglevel Debug severity (ignored if "severity" is not "DEBUG")
/// \param file Name of the local message file.
void initLogger(const std::string& root, isc::log::Severity severity,
int dbglevel, const char* file);
diff --git a/src/lib/log/message_dictionary.cc b/src/lib/log/message_dictionary.cc
index c091369..deb8232 100644
--- a/src/lib/log/message_dictionary.cc
+++ b/src/lib/log/message_dictionary.cc
@@ -109,5 +109,5 @@ MessageDictionary::globalDictionary() {
-} // namspace log
+} // namespace log
} // namespace isc
diff --git a/src/lib/log/message_dictionary.h b/src/lib/log/message_dictionary.h
index 0caa3ea..23f76d7 100644
--- a/src/lib/log/message_dictionary.h
+++ b/src/lib/log/message_dictionary.h
@@ -116,7 +116,7 @@ public:
/// const char* and adds them to the dictionary. The messages are added
/// using "Add".
///
- /// \param data null-terminated array of const char* alternating ID and
+ /// \param elements null-terminated array of const char* alternating ID and
/// message text. This should be an odd number of elements long, the last
/// elemnent being NULL. If it is an even number of elements long, the
/// last ID is ignored.
diff --git a/src/lib/log/message_reader.cc b/src/lib/log/message_reader.cc
index 7ae7ae0..4402b0e 100644
--- a/src/lib/log/message_reader.cc
+++ b/src/lib/log/message_reader.cc
@@ -186,7 +186,7 @@ MessageReader::parseNamespace(const vector<string>& tokens) {
// Process message. By the time this method is called, the line has been
// stripped of leading and trailing spaces, and we believe that it is a line
-// defining a message. The first token on the line is convered to uppercase
+// defining a message. The first token on the line is converted to uppercase
// and becomes the message ID; the rest of the line is the message text.
void
diff --git a/src/lib/log/messagedef.mes b/src/lib/log/messagedef.mes
index 55b3e7c..3599388 100644
--- a/src/lib/log/messagedef.mes
+++ b/src/lib/log/messagedef.mes
@@ -37,12 +37,12 @@ DUPMSGID duplicate message ID (%s) in compiled code
DUPLNS duplicate $NAMESPACE directive found
+ When reading a message file, more than one $NAMESPACE directive was found. In
+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandonded.
++ read will be abandoned.
DUPLPRFX duplicate $PREFIX directive found
+ When reading a message file, more than one $PREFIX directive was found. In
+ this version of the code, such a condition is regarded as an error and the
-+ read will be abandonded.
++ read will be abandoned.
IDNOTFND could not replace message for '%s': no such message identification
+ During start-up a local message file was read. A line with the listed
@@ -53,7 +53,7 @@ IDNOTFND could not replace message for '%s': no such message identification
+ identification has been removed.
+
+ This message may appear a number of times in the file, once for every such
-+ unknown mnessage identification.
++ unknown message identification.
MSGRDERR error reading from message file %s: %s
+ The specified error was encountered reading from the named message file.
@@ -69,9 +69,9 @@ NSEXTRARG $NAMESPACE directive has too many arguments
NSINVARG $NAMESPACE directive has an invalid argument ('%s')
+ The $NAMESPACE argument should be a valid C++ namespace. The reader does a
-+ cursory check on its validity, checking that the characters in the namspace
++ cursory check on its validity, checking that the characters in the namespace
+ are correct. The error is generated when the reader finds an invalid
-+ character. (Valid are alphanumeric characters, underscroes and colons.)
++ character. (Valid are alphanumeric characters, underscores and colons.)
NOMSGTXT a line containing a message ID ('%s') and nothing else was found
+ Message definitions comprise lines starting with a message identification (a
diff --git a/src/lib/log/strutil.h b/src/lib/log/strutil.h
index f44b0d0..087410f 100644
--- a/src/lib/log/strutil.h
+++ b/src/lib/log/strutil.h
@@ -79,7 +79,7 @@ std::vector<std::string> tokens(const std::string& text,
///
/// Used in uppercase() to pass as an argument to std::transform(). The
/// function std::toupper() can't be used as it takes an "int" as its argument;
-/// this confuses the template expansion mechanism because defererencing a
+/// this confuses the template expansion mechanism because dereferencing a
/// string::iterator returns a char.
///
/// \param chr Character to be upper-cased.
@@ -104,7 +104,7 @@ inline void uppercase(std::string& text) {
///
/// Used in lowercase() to pass as an argument to std::transform(). The
/// function std::tolower() can't be used as it takes an "int" as its argument;
-/// this confuses the template expansion mechanism because defererencing a
+/// this confuses the template expansion mechanism because dereferencing a
/// string::iterator returns a char.
///
/// \param chr Character to be lower-cased.
diff --git a/src/lib/log/xdebuglevel.h b/src/lib/log/xdebuglevel.h
index fce3de4..e580b77 100644
--- a/src/lib/log/xdebuglevel.h
+++ b/src/lib/log/xdebuglevel.h
@@ -132,7 +132,7 @@ public:
/// \return Pointer to the desired logging level object.
static LevelPtr toLevel(int val, const LevelPtr& defaultLevel);
- /// \param Convert String to Logging Level
+ /// \brief Convert String to Logging Level
///
/// Returns a logging level object corresponding to the given name. If the
/// name is invalid, an object of logging level DEBUG (the minimum debug
@@ -143,7 +143,7 @@ public:
/// \return Pointer to the desired logging level object.
static LevelPtr toLevelLS(const LogString& sArg);
- /// \param Convert String to Logging Level
+ /// \brief Convert String to Logging Level
///
/// Returns a logging level object corresponding to the given name. If the
/// name is invalid, the given default is returned.
diff --git a/src/lib/nsas/Makefile.am b/src/lib/nsas/Makefile.am
index 04a765b..43300f6 100644
--- a/src/lib/nsas/Makefile.am
+++ b/src/lib/nsas/Makefile.am
@@ -37,5 +37,6 @@ libnsas_la_SOURCES += zone_entry.cc zone_entry.h
libnsas_la_SOURCES += fetchable.h
libnsas_la_SOURCES += address_request_callback.h
libnsas_la_SOURCES += random_number_generator.h
+libnsas_la_SOURCES += glue_hints.h glue_hints.cc
CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/nsas/address_entry.h b/src/lib/nsas/address_entry.h
index 148d479..8698017 100644
--- a/src/lib/nsas/address_entry.h
+++ b/src/lib/nsas/address_entry.h
@@ -21,7 +21,7 @@
/// convenience methods for accessing and updating the information.
#include <stdint.h>
-#include "asiolink.h"
+#include <asiolink/io_address.h>
namespace isc {
namespace nsas {
@@ -39,7 +39,7 @@ public:
{}
/// \return Address object
- asiolink::IOAddress getAddress() const {
+ const asiolink::IOAddress& getAddress() const {
return address_;
}
diff --git a/src/lib/nsas/asiolink.h b/src/lib/nsas/asiolink.h
index f5af192..d95868f 100644
--- a/src/lib/nsas/asiolink.h
+++ b/src/lib/nsas/asiolink.h
@@ -18,41 +18,4 @@
#include <string>
#include <sys/socket.h>
-namespace asiolink {
-
-/// \brief IO Address Dummy Class
-///
-/// As part of ther resolver, Evan has written the asiolink.h file, which
-/// encapsulates some of the boost::asio classes. Until these are checked
-/// into trunk and merged with this branch, these dummy classes should fulfill
-/// their function.
-
-class IOAddress {
-public:
- /// \param address_str String representing the address
- IOAddress(const std::string& address_str) : address_(address_str)
- {}
-
- /// \param Just a virtual destructor
- virtual ~ IOAddress() { }
-
- /// \return Textual representation of the address
- std::string toText() const
- {return address_;}
-
- /// \return Address family of the address
- virtual short getFamily() const {
- return ((address_.find(".") != std::string::npos) ? AF_INET : AF_INET6);
- }
-
- /// \return true if two addresses are equal
- bool equal(const IOAddress& address) const
- {return (toText() == address.toText());}
-
-private:
- std::string address_; ///< Address represented
-};
-
-} // namespace asiolink
-
#endif // __ASIOLINK_H
diff --git a/src/lib/nsas/fetchable.h b/src/lib/nsas/fetchable.h
index e828611..461cfca 100644
--- a/src/lib/nsas/fetchable.h
+++ b/src/lib/nsas/fetchable.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
diff --git a/src/lib/nsas/glue_hints.cc b/src/lib/nsas/glue_hints.cc
new file mode 100644
index 0000000..d4c653a
--- /dev/null
+++ b/src/lib/nsas/glue_hints.cc
@@ -0,0 +1,168 @@
+// 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 "glue_hints.h"
+
+#include <stdlib.h>
+
+#include <dns/rrset.h>
+#include <dns/rdata.h>
+#include <dns/rrtype.h>
+#include <dns/rdataclass.h>
+
+#include <asiolink/io_address.h>
+#include <nsas/nameserver_entry.h>
+
+using namespace isc::dns;
+using namespace isc::nsas;
+
+// This is a simple implementation for finding glue
+//
+// It iterates over the AUTHORITY section of the given Message,
+// and for each NS RR it iterates over the ADDITIONAL section to
+// see if there are A or AAAA records.
+//
+// Of course, this could be done more efficiently. One option is to
+// reverse this; check for A and AAAA records (since those will only
+// be there if there actually is glue, while NS records will be present
+// in any delegation). However, it may be even better to let the
+// Response Classifier decide on glue, while it is validating the packet
+//
+// (er, TODO, so to speak. discuss.)
+
+// Helper functions
+namespace {
+ // Add the contents of the given A or AAAA rrset to the given
+ // addressvector
+ //
+ // This creates an 'dummy' NameserverEntry value, because that
+ // is enforced by NameserverAddress. We may want to reconsider
+ // the need for that (perhaps we can change it so that if it is
+ // NULL, all NSAS-related calls to the NameserverAddress object
+ // become nops)
+ void
+ addRRset(std::vector<NameserverAddress>& addresses,
+ const RRsetPtr rrset)
+ {
+ const std::string ns_name = rrset->getName().toText();
+ RdataIteratorPtr rdi = rrset->getRdataIterator();
+ while (!rdi->isLast()) {
+ AddressEntry entry(asiolink::IOAddress(rdi->getCurrent().toText()));
+ boost::shared_ptr<NameserverEntry> ns_entry(new NameserverEntry(ns_name, rrset->getClass()));
+ NameserverAddress ns_address(ns_entry, entry, V4_ONLY);
+ addresses.push_back(ns_address);
+ rdi->next();
+ }
+ }
+}
+
+namespace isc {
+namespace nsas {
+
+GlueHints::GlueHints(const std::string& zone_name,
+ const isc::dns::Message& delegation_message)
+{
+ for (RRsetIterator rssi = delegation_message.beginSection(Message::SECTION_AUTHORITY);
+ rssi != delegation_message.endSection(Message::SECTION_AUTHORITY);
+ ++rssi) {
+ if ((*rssi)->getType() == RRType::NS() &&
+ (*rssi)->getName().toText() == zone_name) {
+ addGlueForRRset(*rssi, delegation_message);
+ }
+ }
+}
+
+
+bool
+GlueHints::hasGlue(AddressFamily family) const {
+ return ((addresses_v4.size() > 0 && (family == ANY_OK || family == V4_ONLY)) ||
+ (addresses_v6.size() > 0 && (family == ANY_OK || family == V6_ONLY)));
+}
+
+NameserverAddress
+GlueHints::getGlue(AddressFamily family) const {
+ // TODO: once we have a more general random lib, use that. Since
+ // this is simply glue, and we don't need a weighted selection,
+ // for now srandom should be good enough. Once #583 has been merged,
+ // (or better yet, once that one and the weighted random have gone
+ // together in a util lib), we can use that.
+ int max = 0;
+ size_t v4s = addresses_v4.size();
+ size_t v6s = addresses_v6.size();
+
+ if (family == ANY_OK || family == V4_ONLY) {
+ max += v4s;
+ }
+ if (family == ANY_OK || family == V6_ONLY) {
+ max += v6s;
+ }
+
+ assert(max > 0);
+ long int selection = random() % max;
+
+ if (family == ANY_OK) {
+ if (selection < v4s) {
+ return addresses_v4[selection];
+ } else {
+ return addresses_v6[selection-v4s];
+ }
+ } else if (family == V4_ONLY) {
+ return addresses_v4[selection];
+ } else if (family == V6_ONLY) {
+ return addresses_v6[selection];
+ } else {
+ // Unknown family
+ assert(false);
+ // Some compilers want something returned anyway
+ return NameserverAddress();
+ }
+}
+
+// Add the A and AAAA records from the given message for the given
+// NS name to the relevant address vector
+// (A rrsets are added to addresses_v4, AAAA rrsets are added to
+// addresses_v6).
+void
+GlueHints::addGlueForName(const Name& name, const Message& message)
+{
+ for (RRsetIterator rssi = message.beginSection(Message::SECTION_ADDITIONAL);
+ rssi != message.endSection(Message::SECTION_ADDITIONAL);
+ ++rssi) {
+ if ((*rssi)->getName() == name) {
+ if ((*rssi)->getType() == RRType::A()) {
+ addRRset(addresses_v4, *rssi);
+ } else if ((*rssi)->getType() == RRType::AAAA()) {
+ addRRset(addresses_v6, *rssi);
+ }
+ }
+ }
+}
+
+// Add the glue for the given NS RRset in the message to the
+// relevant vectors.
+void
+GlueHints::addGlueForRRset(const RRsetPtr rrset, const Message& message)
+{
+ RdataIteratorPtr rdi = rrset->getRdataIterator();
+ while (!rdi->isLast()) {
+ isc::dns::Name name(dynamic_cast<const rdata::generic::NS&>(
+ rdi->getCurrent()).getNSName());
+ addGlueForName(name, message);
+ rdi->next();
+ }
+}
+
+
+} // namespace nsas
+} // namespace isc
diff --git a/src/lib/nsas/glue_hints.h b/src/lib/nsas/glue_hints.h
new file mode 100644
index 0000000..8e6ecf1
--- /dev/null
+++ b/src/lib/nsas/glue_hints.h
@@ -0,0 +1,71 @@
+// Copyright (C) 2010 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 __GLUE_HINTS_H
+#define __GLUE_HINTS_H
+
+#include <vector>
+
+#include <dns/message.h>
+
+#include "nsas_types.h"
+#include "nameserver_address.h"
+
+namespace isc {
+namespace nsas {
+
+class GlueHints {
+public:
+ /// \brief Empty constructor
+ GlueHints() {};
+
+ /// \brief Constructor
+ ///
+ /// Creates a glue hint object, with the glue data found in the
+ /// given packet.
+ ///
+ /// \param zone_name The name of the zone to find glue for
+ /// \param delegation_message The Message that may contain glue
+ GlueHints(const std::string& zone_name,
+ const isc::dns::Message& delegation_message);
+
+ /// \brief Check if there is glue for the given AddressFamily
+ ///
+ /// \param family the AddressFamily to check for glue for
+ /// \return true if there is glue for that family. false if not
+ bool hasGlue(AddressFamily family) const;
+
+ /// \brief Get a random glue address for the given family
+ ///
+ /// ONLY call this if hasGlue() returned true.
+ ///
+ /// \param family the AddressFamily to get glue for
+ /// \return a NameserverAddress specified by the glue
+ NameserverAddress getGlue(AddressFamily family) const;
+
+private:
+ void addGlueForName(const isc::dns::Name& name,
+ const isc::dns::Message& message);
+ void addGlueForRRset(const isc::dns::RRsetPtr rrset,
+ const isc::dns::Message& message);
+
+ std::vector<NameserverAddress> addresses_v4;
+ std::vector<NameserverAddress> addresses_v6;
+};
+
+}
+}
+
+
+#endif // __GLUE_HINTS_H
diff --git a/src/lib/nsas/hash.h b/src/lib/nsas/hash.h
index 0290c26..85b82c3 100644
--- a/src/lib/nsas/hash.h
+++ b/src/lib/nsas/hash.h
@@ -59,7 +59,7 @@ public:
/// sequence could lead to problems in checking results.
Hash(uint32_t tablesize, uint32_t maxkeylen = 255, bool randomise = true);
- /// \bool Virtual Destructor
+ /// \brief Virtual Destructor
virtual ~Hash()
{}
diff --git a/src/lib/nsas/hash_table.h b/src/lib/nsas/hash_table.h
index e46d687..c4a9913 100644
--- a/src/lib/nsas/hash_table.h
+++ b/src/lib/nsas/hash_table.h
@@ -126,7 +126,7 @@ public:
///
/// Initialises the hash table.
///
- /// \param CmpFn Compare function (or object) used to compare an object with
+ /// \param cmp Compare function (or object) used to compare an object with
/// to get the name to be used as a key in the table. The object should be
/// created via a "new" as ownership passes to the hash table. The hash
/// table will take the responsibility of deleting it.
diff --git a/src/lib/nsas/locks.h b/src/lib/nsas/locks.h
index 98197c3..695ad56 100644
--- a/src/lib/nsas/locks.h
+++ b/src/lib/nsas/locks.h
@@ -26,11 +26,6 @@
/// Note that we need to include <config.h> in our .cc files for that
/// to be set. we might want to enfore this at compile time with a check
/// (TODO)
-/// Note that this also contains a workaround for Sunstudio; which
-/// probably won't completely work right now (that is, if the TODO
-/// above is completed), since that would also require some changes
-/// in most (at first glance unrelated) Makefiles
-/// (TODO2)
#ifndef __LOCKS_
#define __LOCKS_
diff --git a/src/lib/nsas/lru_list.h b/src/lib/nsas/lru_list.h
index 993eb89..b057baf 100644
--- a/src/lib/nsas/lru_list.h
+++ b/src/lib/nsas/lru_list.h
@@ -109,6 +109,13 @@ public:
/// \param element Reference to the element to touch.
virtual void touch(boost::shared_ptr<T>& element);
+ /// \brief Drop All the Elements in the List .
+ ///
+ /// All the elements will be dropped from the list container, and their
+ /// drop handler(if there is one) will be called, when done, the size of
+ /// of list will be 0.
+ virtual void clear();
+
/// \brief Return Size of the List
///
/// An independent count is kept of the list size, as list.size() may take
@@ -133,7 +140,7 @@ public:
/// \brief Set Maximum Size
///
- /// \param new_size New maximum list size
+ /// \param max_size New maximum list size
virtual void setMaxSize(uint32_t max_size) {
max_size_ = max_size;
}
@@ -228,6 +235,25 @@ void LruList<T>::touch(boost::shared_ptr<T>& element) {
}
}
+// Clear the list- when done, the size of list will be 0.
+template <typename T>
+void LruList<T>::clear() {
+ // Protect list against concurrent access
+ isc::locks::scoped_lock<isc::locks::mutex> lock(mutex_);
+
+ // ... and update the count while we have the mutex.
+ count_ = 0;
+ typename std::list<boost::shared_ptr<T> >::iterator iter;
+ if (dropped_) {
+ for (iter = lru_.begin(); iter != lru_.end(); ++iter) {
+ // Call the drop handler.
+ (*dropped_)(iter->get());
+ }
+ }
+
+ lru_.clear();
+}
+
} // namespace nsas
} // namespace isc
diff --git a/src/lib/nsas/nameserver_address.cc b/src/lib/nsas/nameserver_address.cc
index b2ed55c..19d18c5 100644
--- a/src/lib/nsas/nameserver_address.cc
+++ b/src/lib/nsas/nameserver_address.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
@@ -23,7 +23,9 @@ namespace nsas {
void
NameserverAddress::updateRTT(uint32_t rtt) const {
// We delegate it to the address entry inside the nameserver entry
- ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ if (ns_) {
+ ns_->updateAddressRTT(rtt, address_.getAddress(), family_);
+ }
}
} // namespace nsas
diff --git a/src/lib/nsas/nameserver_address.h b/src/lib/nsas/nameserver_address.h
index 7752deb..07b6d4a 100644
--- a/src/lib/nsas/nameserver_address.h
+++ b/src/lib/nsas/nameserver_address.h
@@ -60,10 +60,10 @@ public:
/// pointed to NameserverEntry which contains the address as well as it's
/// corresponding index. The user can update it's RTT with the index later.
///
- /// \param namerserver A shared_ptr that points to a NameserverEntry object
+ /// \param nameserver A shared_ptr that points to a NameserverEntry object
/// the shared_ptr can avoid the NameserverEntry object being dropped while the
/// request is processing.
- /// \param index The address's index in NameserverEntry's addresses vector
+ /// \param address The address's index in NameserverEntry's addresses vector
/// \param family Address family, V4_ONLY or V6_ONLY
NameserverAddress(const boost::shared_ptr<NameserverEntry>& nameserver,
const AddressEntry& address, AddressFamily family):
diff --git a/src/lib/nsas/nameserver_address_store.cc b/src/lib/nsas/nameserver_address_store.cc
index 7bb0eee..e92c177 100644
--- a/src/lib/nsas/nameserver_address_store.cc
+++ b/src/lib/nsas/nameserver_address_store.cc
@@ -29,6 +29,7 @@
#include "nameserver_entry.h"
#include "nameserver_address_store.h"
#include "zone_entry.h"
+#include "glue_hints.h"
#include "address_request_callback.h"
using namespace isc::dns;
@@ -53,7 +54,7 @@ NameserverAddressStore::NameserverAddressStore(
new HashDeleter<ZoneEntry>(*zone_hash_))),
nameserver_lru_(new LruList<NameserverEntry>((3 * nshashsize),
new HashDeleter<NameserverEntry>(*nameserver_hash_))),
- resolver_(resolver)
+ resolver_(resolver.get())
{ }
namespace {
@@ -66,12 +67,12 @@ namespace {
*/
boost::shared_ptr<ZoneEntry>
newZone(
- const boost::shared_ptr<isc::resolve::ResolverInterface>* resolver,
+ isc::resolve::ResolverInterface* resolver,
const string* zone, const RRClass* class_code,
const boost::shared_ptr<HashTable<NameserverEntry> >* ns_hash,
const boost::shared_ptr<LruList<NameserverEntry> >* ns_lru)
{
- boost::shared_ptr<ZoneEntry> result(new ZoneEntry(*resolver, *zone, *class_code,
+ boost::shared_ptr<ZoneEntry> result(new ZoneEntry(resolver, *zone, *class_code,
*ns_hash, *ns_lru));
return (result);
}
@@ -80,17 +81,33 @@ newZone(
void
NameserverAddressStore::lookup(const string& zone, const RRClass& class_code,
- boost::shared_ptr<AddressRequestCallback> callback, AddressFamily family)
+ boost::shared_ptr<AddressRequestCallback> callback, AddressFamily family,
+ const GlueHints& glue_hints)
{
- pair<bool, boost::shared_ptr<ZoneEntry> > zone_obj(zone_hash_->getOrAdd(HashKey(
- zone, class_code), boost::bind(newZone, &resolver_, &zone, &class_code,
- &nameserver_hash_, &nameserver_lru_)));
+ pair<bool, boost::shared_ptr<ZoneEntry> > zone_obj(
+ zone_hash_->getOrAdd(HashKey(zone, class_code),
+ boost::bind(newZone, resolver_, &zone, &class_code,
+ &nameserver_hash_, &nameserver_lru_)));
if (zone_obj.first) {
zone_lru_->add(zone_obj.second);
} else {
zone_lru_->touch(zone_obj.second);
}
- zone_obj.second->addCallback(callback, family);
+
+ zone_obj.second->addCallback(callback, family, glue_hints);
+}
+
+void
+NameserverAddressStore::cancel(const string& zone,
+ const RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family)
+{
+ boost::shared_ptr<ZoneEntry> entry(zone_hash_->get(HashKey(zone,
+ class_code)));
+ if (entry) {
+ entry->removeCallback(callback, family);
+ }
}
} // namespace nsas
diff --git a/src/lib/nsas/nameserver_address_store.h b/src/lib/nsas/nameserver_address_store.h
index f183871..d54be84 100644
--- a/src/lib/nsas/nameserver_address_store.h
+++ b/src/lib/nsas/nameserver_address_store.h
@@ -23,6 +23,7 @@
#include <resolve/resolver_interface.h>
#include "nsas_types.h"
+#include "glue_hints.h"
namespace isc {
// Some forward declarations, so we do not need to include so many headers
@@ -60,7 +61,7 @@ public:
/// tests) should it use to ask questions.
/// \param zonehashsize Size of the zone hash table. The default value of
/// 1009 is the first prime number above 1000.
- /// \param nshash size Size of the nameserver hash table. The default
+ /// \param nshashsize Size of the nameserver hash table. The default
/// value of 3001 is the first prime number over 3000, and by implication,
/// there is an assumption that there will be more nameservers than zones
/// in the store.
@@ -85,7 +86,14 @@ public:
/// \param family Which address is requested.
void lookup(const std::string& zone, const dns::RRClass& class_code,
boost::shared_ptr<AddressRequestCallback> callback, AddressFamily
- family = ANY_OK);
+ family = ANY_OK, const GlueHints& = GlueHints());
+
+ /// \brief cancel the given lookup action
+ ///
+ /// \param callback Callback object that would be called
+ void cancel(const std::string& zone, const dns::RRClass& class_code,
+ const boost::shared_ptr<AddressRequestCallback>& callback,
+ AddressFamily family = ANY_OK);
/// \brief Protected Members
///
@@ -108,7 +116,7 @@ protected:
boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru_;
// The resolver we use
private:
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver_;
+ isc::resolve::ResolverInterface* resolver_;
//}@
};
diff --git a/src/lib/nsas/nameserver_entry.cc b/src/lib/nsas/nameserver_entry.cc
index 9522e81..40d5cd7 100644
--- a/src/lib/nsas/nameserver_entry.cc
+++ b/src/lib/nsas/nameserver_entry.cc
@@ -35,6 +35,8 @@
#include <dns/question.h>
#include <resolve/resolver_interface.h>
+#include <asiolink/io_address.h>
+
#include "address_entry.h"
#include "nameserver_address.h"
#include "nameserver_entry.h"
@@ -140,7 +142,7 @@ NameserverEntry::setAddressRTT(const IOAddress& address, uint32_t rtt) {
AddressFamily family(V4_ONLY);
for (;;) {
BOOST_FOREACH(AddressEntry& entry, addresses_[family]) {
- if (entry.getAddress().equal(address)) {
+ if (entry.getAddress().equals(address)) {
entry.setRTT(rtt);
return;
}
@@ -172,6 +174,9 @@ NameserverEntry::updateAddressRTTAtIndex(uint32_t rtt, size_t index,
uint32_t old_rtt = addresses_[family][index].getRTT();
uint32_t new_rtt = (uint32_t)(old_rtt * UPDATE_RTT_ALPHA + rtt *
(1 - UPDATE_RTT_ALPHA));
+ if (new_rtt == 0) {
+ new_rtt = 1;
+ }
addresses_[family][index].setRTT(new_rtt);
}
@@ -181,7 +186,7 @@ NameserverEntry::updateAddressRTT(uint32_t rtt,
{
Lock lock(mutex_);
for (size_t i(0); i < addresses_[family].size(); ++ i) {
- if (addresses_[family][i].getAddress().equal(address)) {
+ if (addresses_[family][i].getAddress().equals(address)) {
updateAddressRTTAtIndex(rtt, i, family);
return;
}
@@ -226,8 +231,9 @@ class NameserverEntry::ResolverCallback :
response_message->getRcode() != isc::dns::Rcode::NOERROR() ||
response_message->getRRCount(isc::dns::Message::SECTION_ANSWER) == 0) {
failureInternal(lock);
+ return;
}
-
+
isc::dns::RRsetIterator rrsi =
response_message->beginSection(isc::dns::Message::SECTION_ANSWER);
const isc::dns::RRsetPtr response = *rrsi;
@@ -377,8 +383,7 @@ class NameserverEntry::ResolverCallback :
};
void
-NameserverEntry::askIP(
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
const RRType& type, AddressFamily family)
{
QuestionPtr question(new Question(Name(getName()), RRClass(getClass()),
@@ -389,8 +394,7 @@ NameserverEntry::askIP(
}
void
-NameserverEntry::askIP(
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+NameserverEntry::askIP(isc::resolve::ResolverInterface* resolver,
boost::shared_ptr<Callback> callback, AddressFamily family)
{
Lock lock(mutex_);
diff --git a/src/lib/nsas/nameserver_entry.h b/src/lib/nsas/nameserver_entry.h
index f6c2e8c..99d7ff5 100644
--- a/src/lib/nsas/nameserver_entry.h
+++ b/src/lib/nsas/nameserver_entry.h
@@ -151,7 +151,7 @@ public:
/// Updates the RTT for a particular address
///
/// \param address Address to update
- /// \param RTT New RTT for the address
+ /// \param rtt New RTT for the address
void setAddressRTT(const asiolink::IOAddress& address, uint32_t rtt);
/// \brief Update RTT of the address that corresponding to the index
@@ -241,7 +241,7 @@ public:
* even when there are addresses, if there are no addresses for this
* family.
*/
- void askIP(boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ void askIP(isc::resolve::ResolverInterface* resolver,
boost::shared_ptr<Callback> callback, AddressFamily family);
//@}
@@ -273,7 +273,7 @@ private:
/// \short Private version that does the actual asking of one address type
///
/// Call unlocked.
- void askIP(boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ void askIP(isc::resolve::ResolverInterface* resolver,
const isc::dns::RRType&, AddressFamily);
};
diff --git a/src/lib/nsas/random_number_generator.h b/src/lib/nsas/random_number_generator.h
index e80ebcb..8884d0e 100644
--- a/src/lib/nsas/random_number_generator.h
+++ b/src/lib/nsas/random_number_generator.h
@@ -15,8 +15,12 @@
#ifndef __NSAS_RANDOM_NUMBER_GENERATOR_H
#define __NSAS_RANDOM_NUMBER_GENERATOR_H
+#include <algorithm>
#include <cmath>
#include <numeric>
+
+#include <exceptions/exceptions.h>
+
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include <boost/random/uniform_real.hpp>
@@ -25,6 +29,26 @@
namespace isc {
namespace nsas {
+class InvalidLimits : public isc::BadValue {
+public:
+ InvalidLimits(const char* file, size_t line, const char* what) :
+ isc::BadValue(file, line, what) {}
+};
+
+class SumNotOne : public isc::BadValue {
+public:
+ SumNotOne(const char* file, size_t line, const char* what) :
+ isc::BadValue(file, line, what) {}
+};
+
+class InvalidProbValue : public isc::BadValue {
+public:
+ InvalidProbValue(const char* file, size_t line, const char* what) :
+ isc::BadValue(file, line, what) {}
+};
+
+
+
/// \brief Uniform random integer generator
///
/// Generate uniformly distributed integers in range of [min, max]
@@ -35,8 +59,17 @@ public:
/// \param min The minimum number in the range
/// \param max The maximum number in the range
UniformRandomIntegerGenerator(int min, int max):
- min_(min), max_(max), dist_(min, max), generator_(rng_, dist_)
+ min_(std::min(min, max)), max_(std::max(min, max)),
+ dist_(min_, max_), generator_(rng_, dist_)
{
+ // To preserve the restriction of the underlying uniform_int class (and
+ // to retain compatibility with earlier versions of the class), we will
+ // abort if the minimum and maximum given are the wrong way round.
+ if (min > max) {
+ isc_throw(InvalidLimits, "minimum limit is greater than maximum "
+ "when initializing UniformRandomIntegerGenerator");
+ }
+
// Init with the current time
rng_.seed(time(NULL));
}
@@ -73,8 +106,10 @@ public:
size_t min = 0):
dist_(0, 1.0), uniform_real_gen_(rng_, dist_), min_(min)
{
- // The probabilities must be valid
- assert(isProbabilitiesValid(probabilities));
+ // The probabilities must be valid. Checking is quite an expensive
+ // operation, so is only done in a debug build.
+ assert(areProbabilitiesValid(probabilities));
+
// Calculate the partial sum of probabilities
std::partial_sum(probabilities.begin(), probabilities.end(),
std::back_inserter(cumulative_));
@@ -96,8 +131,8 @@ public:
/// \param min The minimum integer that generated
void reset(const std::vector<double>& probabilities, size_t min = 0)
{
- // The probabilities must be valid
- assert(isProbabilitiesValid(probabilities));
+ // The probabilities must be valid.
+ assert(areProbabilitiesValid(probabilities));
// Reset the cumulative sum
cumulative_.clear();
@@ -120,16 +155,24 @@ public:
private:
/// \brief Check the validation of probabilities vector
///
- /// The probability must be in range of [0, 1.0] and the sum must be equal to 1.0
- /// Empty probabilities is also valid.
- bool isProbabilitiesValid(const std::vector<double>& probabilities) const
+ /// The probability must be in range of [0, 1.0] and the sum must be equal
+ /// to 1.0. Empty probabilities are also valid.
+ ///
+ /// Checking the probabilities is quite an expensive operation, so it is
+ /// only done during a debug build (via a call through assert()). However,
+ /// instead of letting assert() call abort(), if this method encounters an
+ /// error, an exception is thrown. This makes unit testing somewhat easier.
+ ///
+ /// \param probabilities Vector of probabilities.
+ bool areProbabilitiesValid(const std::vector<double>& probabilities) const
{
typedef std::vector<double>::const_iterator Iterator;
double sum = probabilities.empty() ? 1.0 : 0.0;
for(Iterator it = probabilities.begin(); it != probabilities.end(); ++it){
//The probability must be in [0, 1.0]
if(*it < 0.0 || *it > 1.0) {
- return false;
+ isc_throw(InvalidProbValue,
+ "probability must be in the range 0..1");
}
sum += *it;
@@ -137,12 +180,16 @@ private:
double epsilon = 0.0001;
// The sum must be equal to 1
- return std::fabs(sum - 1.0) < epsilon;
+ if (std::fabs(sum - 1.0) >= epsilon) {
+ isc_throw(SumNotOne, "Sum of probabilities is not equal to 1");
+ }
+
+ return true;
}
- std::vector<double> cumulative_; ///< The partial sum of the probabilities
- boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator
- boost::uniform_real<> dist_; ///< Uniformly distributed real numbers
+ std::vector<double> cumulative_; ///< Partial sum of the probabilities
+ boost::mt19937 rng_; ///< Mersenne Twister: A 623-dimensionally equidistributed uniform pseudo-random number generator
+ boost::uniform_real<> dist_; ///< Uniformly distributed real numbers
// Shortcut typedef
// This typedef is placed directly before its use, as the sunstudio
diff --git a/src/lib/nsas/tests/Makefile.am b/src/lib/nsas/tests/Makefile.am
index 530b730..9d9e61c 100644
--- a/src/lib/nsas/tests/Makefile.am
+++ b/src/lib/nsas/tests/Makefile.am
@@ -54,6 +54,7 @@ endif
run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
endif
diff --git a/src/lib/nsas/tests/address_entry_unittest.cc b/src/lib/nsas/tests/address_entry_unittest.cc
index 716068c..02fef51 100644
--- a/src/lib/nsas/tests/address_entry_unittest.cc
+++ b/src/lib/nsas/tests/address_entry_unittest.cc
@@ -24,12 +24,12 @@
#include <stdint.h>
-#include "../asiolink.h"
+#include <asiolink/io_address.h>
#include "../address_entry.h"
static std::string V4A_TEXT("1.2.3.4");
static std::string V4B_TEXT("5.6.7.8");
-static std::string V6A_TEXT("2001:dead:beef::0");
+static std::string V6A_TEXT("2001:dead:beef::");
static std::string V6B_TEXT("1984:1985::1986:1987");
using namespace asiolink;
diff --git a/src/lib/nsas/tests/fetchable_unittest.cc b/src/lib/nsas/tests/fetchable_unittest.cc
index f94cd16..4e9f3b4 100644
--- a/src/lib/nsas/tests/fetchable_unittest.cc
+++ b/src/lib/nsas/tests/fetchable_unittest.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
diff --git a/src/lib/nsas/tests/lru_list_unittest.cc b/src/lib/nsas/tests/lru_list_unittest.cc
index 0161f2b..e286826 100644
--- a/src/lib/nsas/tests/lru_list_unittest.cc
+++ b/src/lib/nsas/tests/lru_list_unittest.cc
@@ -251,6 +251,35 @@ TEST_F(LruListTest, Dropped) {
EXPECT_EQ(0, (entry3_->getClass().getCode() & 0x8000));
}
+// Clear functor tests: tests whether all the elements in
+// the list are dropped properly and the size of list is
+// set to 0.
+TEST_F(LruListTest, Clear) {
+ // Create an object with an expiration handler.
+ LruList<TestEntry> lru(3, new Dropped());
+
+ // Fill the list
+ lru.add(entry1_);
+ lru.add(entry2_);
+ lru.add(entry3_);
+
+ EXPECT_EQ(RRClass::IN(), entry1_->getClass());
+ EXPECT_EQ(RRClass::CH(), entry2_->getClass());
+ EXPECT_EQ(RRClass::HS(), entry3_->getClass());
+
+ EXPECT_EQ(0, (entry1_->getClass().getCode() & 0x8000));
+ EXPECT_EQ(0, (entry2_->getClass().getCode() & 0x8000));
+ EXPECT_EQ(0, (entry3_->getClass().getCode() & 0x8000));
+
+ // Clear the lru list, and check the drop handler run
+ lru.clear();
+ EXPECT_NE(0, (entry1_->getClass().getCode() & 0x8000));
+ EXPECT_NE(0, (entry2_->getClass().getCode() & 0x8000));
+ EXPECT_NE(0, (entry3_->getClass().getCode() & 0x8000));
+
+ EXPECT_EQ(0, lru.size());
+}
+
// Miscellaneous tests - pathological conditions
TEST_F(LruListTest, Miscellaneous) {
diff --git a/src/lib/nsas/tests/nameserver_address_store_unittest.cc b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
index 95b46a8..9133daf 100644
--- a/src/lib/nsas/tests/nameserver_address_store_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_store_unittest.cc
@@ -131,7 +131,7 @@ protected:
for (int i = 1; i <= 9; ++i) {
std::string name = "zone" + boost::lexical_cast<std::string>(i);
zones_.push_back(boost::shared_ptr<ZoneEntry>(new ZoneEntry(
- resolver_, name, RRClass(40 + i),
+ resolver_.get(), name, RRClass(40 + i),
boost::shared_ptr<HashTable<NameserverEntry> >(),
boost::shared_ptr<LruList<NameserverEntry> >())));
}
@@ -232,11 +232,9 @@ TEST_F(NameserverAddressStoreTest, NameserverDeletionCheck) {
EXPECT_EQ(1, nameservers_[1].use_count());
}
-/**
- * \short Try lookup on empty store.
- *
- * Check if it asks correct questions and it keeps correct internal state.
- */
+/// \brief Try lookup on empty store.
+///
+/// Check if it asks correct questions and it keeps correct internal state.
TEST_F(NameserverAddressStoreTest, emptyLookup) {
DerivedNsas nsas(resolver_, 10, 10);
// Ask it a question
@@ -268,11 +266,9 @@ TEST_F(NameserverAddressStoreTest, emptyLookup) {
}
}
-/**
- * \short Try looking up a zone that does not have any nameservers.
- *
- * It should not ask anything and say it is unreachable right away.
- */
+/// \brief Try looking up a zone that does not have any nameservers.
+///
+/// It should not ask anything and say it is unreachable right away.
TEST_F(NameserverAddressStoreTest, zoneWithoutNameservers) {
DerivedNsas nsas(resolver_, 10, 10);
// Ask it a question
@@ -285,13 +281,11 @@ TEST_F(NameserverAddressStoreTest, zoneWithoutNameservers) {
EXPECT_FALSE(NSASCallback::results[0].first);
}
-/**
- * \short Try looking up a zone that has only an unreachable nameserver.
- *
- * It should be unreachable. Furthermore, subsequent questions for that zone
- * or other zone with the same nameserver should be unreachable right away,
- * without further asking.
- */
+/// \brief Try looking up a zone that has only an unreachable nameserver.
+///
+/// It should be unreachable. Furthermore, subsequent questions for that zone
+/// or other zone with the same nameserver should be unreachable right away,
+/// without further asking.
TEST_F(NameserverAddressStoreTest, unreachableNS) {
DerivedNsas nsas(resolver_, 10, 10);
// Ask it a question
@@ -326,12 +320,10 @@ TEST_F(NameserverAddressStoreTest, unreachableNS) {
}
}
-/**
- * \short Try to stress it little bit by having multiple zones and nameservers.
- *
- * Does some asking, on a set of zones that share some nameservers, with
- * slower answering, evicting data, etc.
- */
+/// \short Try to stress it little bit by having multiple zones and nameservers.
+///
+/// Does some asking, on a set of zones that share some nameservers, with
+/// slower answering, evicting data, etc.
TEST_F(NameserverAddressStoreTest, CombinedTest) {
// Create small caches, so we get some evictions
DerivedNsas nsas(resolver_, 1, 1);
diff --git a/src/lib/nsas/tests/nameserver_address_unittest.cc b/src/lib/nsas/tests/nameserver_address_unittest.cc
index 35a46f0..457e61c 100644
--- a/src/lib/nsas/tests/nameserver_address_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_address_unittest.cc
@@ -39,7 +39,9 @@ class NameserverEntrySample {
public:
NameserverEntrySample():
name_("example.org"),
- rrv4_(new RRset(name_, RRClass::IN(), RRType::A(), RRTTL(1200)))
+ rrv4_(new RRset(name_, RRClass::IN(), RRType::A(), RRTTL(1200))),
+ ns_(new NameserverEntry(name_.toText(), RRClass::IN())),
+ resolver_(new TestResolver())
{
// Add some sample A records
rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("1.2.3.4")));
@@ -47,10 +49,9 @@ public:
rrv4_->addRdata(ConstRdataPtr(new RdataTest<A>("9.10.11.12")));
ns_.reset(new NameserverEntry(name_.toText(), RRClass::IN()));
- boost::shared_ptr<TestResolver> resolver(new TestResolver);
- ns_->askIP(resolver, boost::shared_ptr<Callback>(new Callback), ANY_OK);
- resolver->asksIPs(name_, 0, 1);
- resolver->requests[0].second->success(createResponseMessage(rrv4_));
+ ns_->askIP(resolver_.get(), boost::shared_ptr<Callback>(new Callback), ANY_OK);
+ resolver_->asksIPs(name_, 0, 1);
+ resolver_->requests[0].second->success(createResponseMessage(rrv4_));
}
// Return the sample NameserverEntry
@@ -75,6 +76,7 @@ private:
Name name_; ///< Name of the sample
RRsetPtr rrv4_; ///< Standard RRSet - IN, A, lowercase name
boost::shared_ptr<NameserverEntry> ns_; ///< Shared_ptr that points to a NameserverEntry object
+ boost::shared_ptr<TestResolver> resolver_;
class Callback : public NameserverEntry::Callback {
public:
@@ -100,7 +102,7 @@ protected:
// Test that the address is equal to the address in NameserverEntry
TEST_F(NameserverAddressTest, Address) {
- EXPECT_TRUE(ns_address_.getAddress().equal( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
+ EXPECT_TRUE(ns_address_.getAddress().equals( ns_sample_.getAddressAtIndex(TEST_ADDRESS_INDEX)));
boost::shared_ptr<NameserverEntry> empty_ne((NameserverEntry*)NULL);
// It will throw an NullNameserverEntryPointer exception with the empty NameserverEntry shared pointer
diff --git a/src/lib/nsas/tests/nameserver_entry_unittest.cc b/src/lib/nsas/tests/nameserver_entry_unittest.cc
index 9e4cec7..4225e87 100644
--- a/src/lib/nsas/tests/nameserver_entry_unittest.cc
+++ b/src/lib/nsas/tests/nameserver_entry_unittest.cc
@@ -86,7 +86,7 @@ protected:
boost::shared_ptr<TestResolver> resolver(new TestResolver);
boost::shared_ptr<Callback> callback(new Callback);
// Let it ask for data
- entry->askIP(resolver, callback, ANY_OK);
+ entry->askIP(resolver.get(), callback, ANY_OK);
// Check it really asked and sort the queries
EXPECT_TRUE(resolver->asksIPs(Name(entry->getName()), 0, 1));
// Respond with answers
@@ -153,7 +153,7 @@ TEST_F(NameserverEntryTest, SetRTT) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_EQ(i->getAddressEntry().getRTT(), new_rtt);
}
@@ -188,7 +188,7 @@ TEST_F(NameserverEntryTest, Unreachable) {
int matchcount = 0;
for (NameserverEntry::AddressVectorIterator i = newvec.begin();
i != newvec.end(); ++i) {
- if (i->getAddress().equal(first_address)) {
+ if (i->getAddress().equals(first_address)) {
++matchcount;
EXPECT_TRUE(i->getAddressEntry().isUnreachable());
}
@@ -266,7 +266,7 @@ TEST_F(NameserverEntryTest, IPCallbacks) {
boost::shared_ptr<Callback> callback(new Callback);
boost::shared_ptr<TestResolver> resolver(new TestResolver);
- entry->askIP(resolver, callback, ANY_OK);
+ entry->askIP(resolver.get(), callback, ANY_OK);
// Ensure it becomes IN_PROGRESS
EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
// Now, there should be two queries in the resolver
@@ -274,12 +274,12 @@ TEST_F(NameserverEntryTest, IPCallbacks) {
ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
// Another one might ask
- entry->askIP(resolver, callback, V4_ONLY);
+ entry->askIP(resolver.get(), callback, V4_ONLY);
// There should still be only two queries in the resolver
ASSERT_EQ(2, resolver->requests.size());
// Another one, with need of IPv6 address
- entry->askIP(resolver, callback, V6_ONLY);
+ entry->askIP(resolver.get(), callback, V6_ONLY);
// Answer one and see that the callbacks are called
resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
@@ -316,7 +316,7 @@ TEST_F(NameserverEntryTest, IPCallbacksUnreachable) {
boost::shared_ptr<TestResolver> resolver(new TestResolver);
// Ask for its IP
- entry->askIP(resolver, callback, ANY_OK);
+ entry->askIP(resolver.get(), callback, ANY_OK);
// Check it asks the resolver
EXPECT_EQ(2, resolver->requests.size());
ASSERT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
@@ -352,7 +352,7 @@ TEST_F(NameserverEntryTest, DirectAnswer) {
RRType::AAAA()), RRsetPtr());
// A successfull test first
- entry->askIP(resolver, callback, ANY_OK);
+ entry->askIP(resolver.get(), callback, ANY_OK);
EXPECT_EQ(0, resolver->requests.size());
EXPECT_EQ(1, callback->count);
NameserverEntry::AddressVector addresses;
@@ -362,7 +362,7 @@ TEST_F(NameserverEntryTest, DirectAnswer) {
// An unsuccessfull test
callback->count = 0;
entry.reset(new NameserverEntry(EXAMPLE_NET, RRClass::IN()));
- entry->askIP(resolver, callback, ANY_OK);
+ entry->askIP(resolver.get(), callback, ANY_OK);
EXPECT_EQ(0, resolver->requests.size());
EXPECT_EQ(1, callback->count);
addresses.clear();
@@ -381,8 +381,8 @@ TEST_F(NameserverEntryTest, ChangedExpired) {
boost::shared_ptr<TestResolver> resolver(new TestResolver);
// Ask the first time
- entry->askIP(resolver, callback, V4_ONLY);
- entry->askIP(resolver, callback, V6_ONLY);
+ entry->askIP(resolver.get(), callback, V4_ONLY);
+ entry->askIP(resolver.get(), callback, V6_ONLY);
EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
@@ -402,8 +402,8 @@ TEST_F(NameserverEntryTest, ChangedExpired) {
// Ask the second time. The callbacks should not fire right away and it
// should request the addresses again
- entry->askIP(resolver, callback, V4_ONLY);
- entry->askIP(resolver, callback, V6_ONLY);
+ entry->askIP(resolver.get(), callback, V4_ONLY);
+ entry->askIP(resolver.get(), callback, V6_ONLY);
EXPECT_EQ(2, callback->count);
EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3));
EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
@@ -431,8 +431,8 @@ TEST_F(NameserverEntryTest, KeepRTT) {
boost::shared_ptr<TestResolver> resolver(new TestResolver);
// Ask the first time
- entry->askIP(resolver, callback, V4_ONLY);
- entry->askIP(resolver, callback, V6_ONLY);
+ entry->askIP(resolver.get(), callback, V4_ONLY);
+ entry->askIP(resolver.get(), callback, V6_ONLY);
EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 0, 1));
EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
resolver->answer(0, Name(EXAMPLE_CO_UK), RRType::A(),
@@ -455,8 +455,8 @@ TEST_F(NameserverEntryTest, KeepRTT) {
// Ask the second time. The callbacks should not fire right away and it
// should request the addresses again
- entry->askIP(resolver, callback, V4_ONLY);
- entry->askIP(resolver, callback, V6_ONLY);
+ entry->askIP(resolver.get(), callback, V4_ONLY);
+ entry->askIP(resolver.get(), callback, V6_ONLY);
EXPECT_EQ(2, callback->count);
EXPECT_TRUE(resolver->asksIPs(Name(EXAMPLE_CO_UK), 2, 3));
EXPECT_EQ(Fetchable::IN_PROGRESS, entry->getState());
diff --git a/src/lib/nsas/tests/nsas_test.h b/src/lib/nsas/tests/nsas_test.h
index 926e859..7500fc7 100644
--- a/src/lib/nsas/tests/nsas_test.h
+++ b/src/lib/nsas/tests/nsas_test.h
@@ -222,11 +222,6 @@ private:
static const uint32_t HASHTABLE_DEFAULT_SIZE = 1009; ///< First prime above 1000
-} // namespace nsas
-} // namespace isc
-
-namespace {
-
using namespace std;
/*
@@ -245,6 +240,18 @@ class TestResolver : public isc::resolve::ResolverInterface {
public:
typedef pair<QuestionPtr, CallbackPtr> Request;
vector<Request> requests;
+
+ /// \brief Destructor
+ ///
+ /// This is important. All callbacks in the requests vector must be
+ /// called to remove them from internal loops. Without this, destroying
+ /// the NSAS object will leave memory assigned.
+ ~TestResolver() {
+ for (size_t i = 0; i < requests.size(); ++i) {
+ requests[i].second->failure();
+ }
+ }
+
virtual void resolve(const QuestionPtr& q, const CallbackPtr& c) {
PresetAnswers::iterator it(answers_.find(*q));
if (it == answers_.end()) {
@@ -420,6 +427,7 @@ protected:
Name ns_name_; ///< Nameserver name of ns.example.net
};
-} // Empty namespace
+} // namespace nsas
+} // namespace isc
#endif // __NSAS_TEST_H
diff --git a/src/lib/nsas/tests/random_number_generator_unittest.cc b/src/lib/nsas/tests/random_number_generator_unittest.cc
index c306b09..85cbcbf 100644
--- a/src/lib/nsas/tests/random_number_generator_unittest.cc
+++ b/src/lib/nsas/tests/random_number_generator_unittest.cc
@@ -59,11 +59,11 @@ private:
// non-debug environment.
// Note: the death test is not supported by all platforms. We need to
// compile tests using it selectively.
-#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(NDEBUG)
// Test of the constructor
TEST_F(UniformRandomIntegerGeneratorTest, Constructor) {
// The range must be min<=max
- ASSERT_DEATH(UniformRandomIntegerGenerator(3, 2), "");
+ ASSERT_THROW(UniformRandomIntegerGenerator(3, 2), InvalidLimits);
}
#endif
@@ -109,30 +109,32 @@ TEST_F(WeightedRandomIntegerGeneratorTest, Constructor) {
/// the tests will be failed since assert() is non-op in non-debug version.
/// The "#ifndef NDEBUG" is added to make the tests be performed only in
/// non-debug environment.
-#if !defined(NDEBUG) && defined(GTEST_HAS_DEATH_TEST)
+#if !defined(NDEBUG)
//The probability must be >= 0
probabilities.push_back(-0.1);
probabilities.push_back(1.1);
- ASSERT_DEATH(WeightedRandomIntegerGenerator gen2(probabilities), "");
+ ASSERT_THROW(WeightedRandomIntegerGenerator gen2(probabilities),
+ InvalidProbValue);
//The probability must be <= 1.0
probabilities.clear();
probabilities.push_back(0.1);
probabilities.push_back(1.1);
- ASSERT_DEATH(WeightedRandomIntegerGenerator gen3(probabilities), "");
+ ASSERT_THROW(WeightedRandomIntegerGenerator gen3(probabilities),
+ InvalidProbValue);
//The sum must be equal to 1.0
probabilities.clear();
probabilities.push_back(0.2);
probabilities.push_back(0.9);
- ASSERT_DEATH(WeightedRandomIntegerGenerator gen4(probabilities), "");
+ ASSERT_THROW(WeightedRandomIntegerGenerator gen4(probabilities), SumNotOne);
//The sum must be equal to 1.0
probabilities.clear();
probabilities.push_back(0.3);
probabilities.push_back(0.2);
probabilities.push_back(0.1);
- ASSERT_DEATH(WeightedRandomIntegerGenerator gen5(probabilities), "");
+ ASSERT_THROW(WeightedRandomIntegerGenerator gen5(probabilities), SumNotOne);
#endif
}
diff --git a/src/lib/nsas/tests/zone_entry_unittest.cc b/src/lib/nsas/tests/zone_entry_unittest.cc
index 8a3c6f2..34f995c 100644
--- a/src/lib/nsas/tests/zone_entry_unittest.cc
+++ b/src/lib/nsas/tests/zone_entry_unittest.cc
@@ -47,7 +47,7 @@ class InheritedZoneEntry : public ZoneEntry {
const std::string& name, const RRClass& class_code,
boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
- ZoneEntry(resolver, name, class_code, nameserver_table,
+ ZoneEntry(resolver.get(), name, class_code, nameserver_table,
nameserver_lru)
{ }
NameserverVector& nameservers() { return nameservers_; }
@@ -165,9 +165,9 @@ protected:
EXPECT_EQ(failure_count, callback_->unreachable_count_);
EXPECT_EQ(success_count, callback_->successes_.size());
for (size_t i = 0; i < callback_->successes_.size(); ++ i) {
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[i].getAddress()) ||
- IOAddress("2001:db8::1").equal(
+ IOAddress("2001:db8::1").equals(
callback_->successes_[i].getAddress()));
}
}
@@ -234,7 +234,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_NO_THROW(resolver_->answer(2, ns_name_, RRType::AAAA(),
rdata::in::AAAA("2001:db8::1")));
@@ -257,7 +257,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_NO_THROW(resolver_->answer(4, different_name, RRType::A(),
rdata::in::A("192.0.2.2")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.2").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.2").equals(
callback_->successes_[1].getAddress()));
// And now, switch back, as it timed out again
@@ -270,7 +270,7 @@ TEST_F(ZoneEntryTest, ChangedNS) {
EXPECT_EQ(7, resolver_->requests.size());
EXPECT_EQ(Fetchable::READY, zone->getState());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
}
@@ -306,9 +306,9 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
rdata::in::A("192.0.2.1")));
// Two are answered (ANY and V4)
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
// None are rejected
EXPECT_EQ(0, callback_->unreachable_count_);
@@ -318,7 +318,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
// This should answer the third callback
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[2].getAddress()));
// It should think it is ready
EXPECT_EQ(Fetchable::READY, zone->getState());
@@ -326,7 +326,7 @@ TEST_F(ZoneEntryTest, CallbacksAnswered) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(4, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[3].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
}
@@ -366,9 +366,9 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
EXPECT_NO_THROW(resolver_->answer(1, ns_name_, RRType::A(),
rdata::in::A("192.0.2.1")));
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
// Everything arriwed, so we are ready
@@ -377,7 +377,7 @@ TEST_F(ZoneEntryTest, CallbacksAOnly) {
zone->addCallback(callback_, V4_ONLY);
EXPECT_EQ(3, resolver_->requests.size());
ASSERT_EQ(3, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[2].getAddress()));
EXPECT_EQ(1, callback_->unreachable_count_);
@@ -439,9 +439,9 @@ TEST_F(ZoneEntryTest, CallbackTwoNS) {
// The other callbacks should be answered now
EXPECT_EQ(2, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[0].getAddress()));
- EXPECT_TRUE(IOAddress("2001:db8::1").equal(
+ EXPECT_TRUE(IOAddress("2001:db8::1").equals(
callback_->successes_[1].getAddress()));
}
@@ -534,7 +534,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
// It answers, not rejects
ASSERT_EQ(1, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[0].getAddress()));
EXPECT_EQ(0, callback_->unreachable_count_);
// As well with IPv6
@@ -551,7 +551,7 @@ TEST_F(ZoneEntryTest, AddressTimeout) {
rdata::in::A("192.0.2.1"), 0));
EXPECT_EQ(0, callback_->unreachable_count_);
ASSERT_EQ(2, callback_->successes_.size());
- EXPECT_TRUE(IOAddress("192.0.2.1").equal(
+ EXPECT_TRUE(IOAddress("192.0.2.1").equals(
callback_->successes_[1].getAddress()));
}
@@ -569,7 +569,7 @@ TEST_F(ZoneEntryTest, NameserverEntryReady) {
// Inject the entry
boost::shared_ptr<NameserverEntry> nse(injectEntry());
// Fill it with data
- nse->askIP(resolver_, nseCallback(), ANY_OK);
+ nse->askIP(resolver_.get(), nseCallback(), ANY_OK);
EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
@@ -594,7 +594,7 @@ TEST_F(ZoneEntryTest, NameserverEntryNotAsked) {
TEST_F(ZoneEntryTest, NameserverEntryInProgress) {
// Prepare the nameserver entry
boost::shared_ptr<NameserverEntry> nse(injectEntry());
- nse->askIP(resolver_, nseCallback(), ANY_OK);
+ nse->askIP(resolver_.get(), nseCallback(), ANY_OK);
EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
@@ -604,7 +604,7 @@ TEST_F(ZoneEntryTest, NameserverEntryInProgress) {
/// \short Check Zone's reaction to found expired nameserver
TEST_F(ZoneEntryTest, NameserverEntryExpired) {
boost::shared_ptr<NameserverEntry> nse(injectEntry());
- nse->askIP(resolver_, nseCallback(), ANY_OK);
+ nse->askIP(resolver_.get(), nseCallback(), ANY_OK);
EXPECT_EQ(Fetchable::IN_PROGRESS, nse->getState());
EXPECT_TRUE(resolver_->asksIPs(ns_name_, 0, 1));
EXPECT_NO_THROW(resolver_->answer(0, ns_name_, RRType::A(),
@@ -623,7 +623,7 @@ TEST_F(ZoneEntryTest, NameserverEntryExpired) {
/// \short Check how it reacts to an unreachable zone already in the table
TEST_F(ZoneEntryTest, NameserverEntryUnreachable) {
boost::shared_ptr<NameserverEntry> nse(injectEntry());
- nse->askIP(resolver_, nseCallback(), ANY_OK);
+ nse->askIP(resolver_.get(), nseCallback(), ANY_OK);
ASSERT_EQ(2, resolver_->requests.size());
resolver_->requests[0].second->failure();
resolver_->requests[1].second->failure();
diff --git a/src/lib/nsas/zone_entry.cc b/src/lib/nsas/zone_entry.cc
index 77f3dad..3af70a8 100644
--- a/src/lib/nsas/zone_entry.cc
+++ b/src/lib/nsas/zone_entry.cc
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
@@ -36,7 +36,7 @@ using namespace dns;
namespace nsas {
ZoneEntry::ZoneEntry(
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ isc::resolve::ResolverInterface* resolver,
const std::string& name, const isc::dns::RRClass& class_code,
boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru) :
@@ -122,7 +122,7 @@ class ZoneEntry::ResolverCallback :
* do), so we can just reuse them instead of looking them up in
* the table or creating them.
*/
- map<string, NameserverPtr> old;
+ std::map<string, NameserverPtr> old;
BOOST_FOREACH(const NameserverPtr& ptr, entry_->nameservers_) {
old[ptr->getName()] = ptr;
}
@@ -143,7 +143,7 @@ class ZoneEntry::ResolverCallback :
Name ns_name(dynamic_cast<const rdata::generic::NS&>(
iterator->getCurrent()).getNSName());
// Try to find it in the old ones
- map<string, NameserverPtr>::iterator old_ns(old.find(
+ std::map<string, NameserverPtr>::iterator old_ns(old.find(
ns_name.toText()));
/*
* We didn't have this nameserver before. So we just
@@ -224,7 +224,8 @@ class ZoneEntry::ResolverCallback :
};
void
-ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
+ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family,
+ const GlueHints& glue_hints) {
Lock lock(mutex_);
bool ask(false);
@@ -238,11 +239,18 @@ ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
if (getState() == EXPIRED || getState() == NOT_ASKED) {
ask = true;
}
-
+
// We do not have the answer right away, just queue the callback
bool execute(!ask && getState() != IN_PROGRESS &&
callbacks_[family].empty());
- callbacks_[family].push_back(callback);
+
+ // Unless there was glue
+ if (ask && glue_hints.hasGlue(family)) {
+ callback->success(glue_hints.getGlue(family));
+ } else {
+ callbacks_[family].push_back(callback);
+ }
+
if (execute) {
// Try to process it right away, store if not possible to handle
process(family, NameserverPtr());
@@ -261,6 +269,23 @@ ZoneEntry::addCallback(CallbackPtr callback, AddressFamily family) {
}
}
+void
+ZoneEntry::removeCallback(const CallbackPtr& callback, AddressFamily family) {
+ Lock lock(mutex_);
+ std::vector<boost::shared_ptr<AddressRequestCallback> >::iterator i =
+ callbacks_[family].begin();
+ for (; i != callbacks_[family].end(); ++i) {
+ if (*i == callback) {
+ callbacks_[family].erase(i);
+ // At this point, a callback should only be in the list
+ // once (enforced by RunningQuery doing only one at a time)
+ // If that changes, we need to revise this (can't delete
+ // elements from a list we're looping over)
+ return;
+ }
+ }
+}
+
namespace {
// This just moves items from one container to another
diff --git a/src/lib/nsas/zone_entry.h b/src/lib/nsas/zone_entry.h
index 28a42ea..92ac75a 100644
--- a/src/lib/nsas/zone_entry.h
+++ b/src/lib/nsas/zone_entry.h
@@ -32,6 +32,7 @@
#include "fetchable.h"
#include "nsas_types.h"
#include "random_number_generator.h"
+#include "glue_hints.h"
namespace isc {
namespace nsas {
@@ -68,8 +69,7 @@ public:
* \todo Move to cc file, include the lookup (if NSAS uses resolver for
* everything)
*/
- ZoneEntry(
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver,
+ ZoneEntry(isc::resolve::ResolverInterface* resolver,
const std::string& name, const isc::dns::RRClass& class_code,
boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table,
boost::shared_ptr<LruList<NameserverEntry> > nameserver_lru);
@@ -97,9 +97,22 @@ public:
*
* \param callback The callback itself.
* \param family Which address family is acceptable as an answer?
+ * \param glue_hints If a non-empty glue-hints object is passed,
+ * and the NSAS does not have an immediate answer, it will
+ * call back immediately with one of the glue hints.
*/
void addCallback(boost::shared_ptr<AddressRequestCallback>
- callback, AddressFamily family);
+ callback, AddressFamily family,
+ const GlueHints& glue_hints = GlueHints());
+
+ /**
+ * \short Remove a callback from the list
+ *
+ * \param callback The callback itself.
+ * \param family Which address family is acceptable as an answer?
+ */
+ void removeCallback(const boost::shared_ptr<AddressRequestCallback>&
+ callback, AddressFamily family);
/// \short Protected members, so they can be accessed by tests.
//@{
@@ -144,7 +157,7 @@ private:
void process(AddressFamily family,
const boost::shared_ptr<NameserverEntry>& nameserver);
// Resolver we use
- boost::shared_ptr<isc::resolve::ResolverInterface> resolver_;
+ isc::resolve::ResolverInterface* resolver_;
// We store the nameserver table and lru, so we can look up when there's
// update
boost::shared_ptr<HashTable<NameserverEntry> > nameserver_table_;
diff --git a/src/lib/python/isc/Makefile.am b/src/lib/python/isc/Makefile.am
index bda911b..7a54909 100644
--- a/src/lib/python/isc/Makefile.am
+++ b/src/lib/python/isc/Makefile.am
@@ -1,4 +1,4 @@
-SUBDIRS = datasrc cc config log net notify util
+SUBDIRS = datasrc cc config log net notify util testutils
python_PYTHON = __init__.py
diff --git a/src/lib/python/isc/config/ccsession.py b/src/lib/python/isc/config/ccsession.py
index 0e602b7..226c6ba 100644
--- a/src/lib/python/isc/config/ccsession.py
+++ b/src/lib/python/isc/config/ccsession.py
@@ -1,5 +1,4 @@
# Copyright (C) 2009 Internet Systems Consortium.
-# Copyright (C) 2010 CZ NIC
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
@@ -152,6 +151,9 @@ class ModuleCCSession(ConfigData):
self._remote_module_configs = {}
def __del__(self):
+ # If the CC Session obejct has been closed, it returns
+ # immediately.
+ if self._session._closed: return
self._session.group_unsubscribe(self._module_name, "*")
for module_name in self._remote_module_configs:
self._session.group_unsubscribe(module_name)
diff --git a/src/lib/python/isc/config/cfgmgr.py b/src/lib/python/isc/config/cfgmgr.py
index e347f6a..8561378 100644
--- a/src/lib/python/isc/config/cfgmgr.py
+++ b/src/lib/python/isc/config/cfgmgr.py
@@ -44,25 +44,36 @@ class ConfigManagerData:
"""This class hold the actual configuration information, and
reads it from and writes it to persistent storage"""
- def __init__(self, data_path, file_name = "b10-config.db"):
+ def __init__(self, data_path, file_name):
"""Initialize the data for the configuration manager, and
set the version and path for the data store. Initializing
this does not yet read the database, a call to
- read_from_file is needed for that."""
+ read_from_file is needed for that.
+
+ In case the file_name is absolute, data_path is ignored
+ and the directory where the file_name lives is used instead.
+ """
self.data = {}
self.data['version'] = config_data.BIND10_CONFIG_DATA_VERSION
- self.data_path = data_path
- self.db_filename = data_path + os.sep + file_name
+ if os.path.isabs(file_name):
+ self.db_filename = file_name
+ self.data_path = os.path.dirname(file_name)
+ else:
+ self.db_filename = data_path + os.sep + file_name
+ self.data_path = data_path
+
+ def read_from_file(data_path, file_name):
+ """Read the current configuration found in the file file_name.
+ If file_name is absolute, data_path is ignored. Otherwise
+ we look for the file_name in data_path directory.
- def read_from_file(data_path, file_name = "b10-config.db"):
- """Read the current configuration found in the file at
- data_path. If the file does not exist, a
- ConfigManagerDataEmpty exception is raised. If there is a
- parse error, or if the data in the file has the wrong
- version, a ConfigManagerDataReadError is raised. In the first
- case, it is probably safe to log and ignore. In the case of
- the second exception, the best way is probably to report the
- error and stop loading the system."""
+ If the file does not exist, a ConfigManagerDataEmpty exception is
+ raised. If there is a parse error, or if the data in the file has
+ the wrong version, a ConfigManagerDataReadError is raised. In the
+ first case, it is probably safe to log and ignore. In the case of
+ the second exception, the best way is probably to report the error
+ and stop loading the system.
+ """
config = ConfigManagerData(data_path, file_name)
file = None
try:
@@ -142,20 +153,24 @@ class ConfigManagerData:
class ConfigManager:
"""Creates a configuration manager. The data_path is the path
- to the directory containing the b10-config.db file.
+ to the directory containing the configuraton file,
+ database_filename points to the configuration file.
If session is set, this will be used as the communication
channel session. If not, a new session will be created.
The ability to specify a custom session is for testing purposes
and should not be needed for normal usage."""
- def __init__(self, data_path, session = None):
+ def __init__(self, data_path, database_filename, session=None):
"""Initialize the configuration manager. The data_path string
is the path to the directory where the configuration is
- stored (in <data_path>/b10-config.db). Session is an optional
+ stored (in <data_path>/<database_filename> or in
+ <database_filename>, if it is absolute). The dabase_filename
+ is the config file to load. Session is an optional
cc-channel session. If this is not given, a new one is
- created"""
+ created."""
self.data_path = data_path
+ self.database_filename = database_filename
self.module_specs = {}
- self.config = ConfigManagerData(data_path)
+ self.config = ConfigManagerData(data_path, database_filename)
if session:
self.cc = session
else:
@@ -223,17 +238,18 @@ class ConfigManager:
return commands
def read_config(self):
- """Read the current configuration from the b10-config.db file
- at the path specificied at init()"""
+ """Read the current configuration from the file specificied at init()"""
try:
- self.config = ConfigManagerData.read_from_file(self.data_path)
+ self.config = ConfigManagerData.read_from_file(self.data_path,
+ self.\
+ database_filename)
except ConfigManagerDataEmpty:
# ok, just start with an empty config
- self.config = ConfigManagerData(self.data_path)
+ self.config = ConfigManagerData(self.data_path,
+ self.database_filename)
def write_config(self):
- """Write the current configuration to the b10-config.db file
- at the path specificied at init()"""
+ """Write the current configuration to the file specificied at init()"""
self.config.write_to_file()
def _handle_get_module_spec(self, cmd):
diff --git a/src/lib/python/isc/config/config_data.py b/src/lib/python/isc/config/config_data.py
index 582c11c..cee1d34 100644
--- a/src/lib/python/isc/config/config_data.py
+++ b/src/lib/python/isc/config/config_data.py
@@ -461,8 +461,8 @@ class MultiConfigData:
spec_part_list = spec_part['list_item_spec']
list_value, status = self.get_value(identifier)
if list_value is None:
- print("Error: identifier '%s' not found" % identifier)
- return
+ raise isc.cc.data.DataNotFoundError(identifier)
+
if type(list_value) != list:
# the identifier specified a single element
self._append_value_item(result, spec_part_list, identifier, all)
diff --git a/src/lib/python/isc/config/tests/ccsession_test.py b/src/lib/python/isc/config/tests/ccsession_test.py
index 2ae37f5..4edc559 100644
--- a/src/lib/python/isc/config/tests/ccsession_test.py
+++ b/src/lib/python/isc/config/tests/ccsession_test.py
@@ -234,7 +234,18 @@ class TestModuleCCSession(unittest.TestCase):
fake_session = FakeModuleCCSession()
mccs = self.create_session("spec1.spec", None, None, fake_session)
mccs.close()
- self.assertEqual("closed", fake_session._socket)
+ self.assertEqual(None, fake_session._socket)
+
+ def test_del_opened(self):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ mccs.__del__() # with opened fake_session
+
+ def test_del_closed(self):
+ fake_session = FakeModuleCCSession()
+ mccs = self.create_session("spec1.spec", None, None, fake_session)
+ fake_session.close()
+ mccs.__del__() # with closed fake_session
def my_config_handler_ok(self, new_config):
return isc.config.ccsession.create_answer(0)
diff --git a/src/lib/python/isc/config/tests/cfgmgr_test.py b/src/lib/python/isc/config/tests/cfgmgr_test.py
index c992d0d..9534e14 100644
--- a/src/lib/python/isc/config/tests/cfgmgr_test.py
+++ b/src/lib/python/isc/config/tests/cfgmgr_test.py
@@ -27,9 +27,20 @@ class TestConfigManagerData(unittest.TestCase):
def setUp(self):
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
- self.config_manager_data = ConfigManagerData(self.writable_data_path)
+ self.config_manager_data = ConfigManagerData(self.writable_data_path,
+ file_name="b10-config.db")
self.assert_(self.config_manager_data)
+ def test_abs_file(self):
+ """
+ Test what happens if we give the config manager an absolute path.
+ It shouldn't append the data path to it.
+ """
+ abs_path = self.data_path + os.sep + "b10-config-imaginary.db"
+ data = ConfigManagerData(os.getcwd(), abs_path)
+ self.assertEqual(abs_path, data.db_filename)
+ self.assertEqual(self.data_path, data.data_path)
+
def test_init(self):
self.assertEqual(self.config_manager_data.data['version'],
config_data.BIND10_CONFIG_DATA_VERSION)
@@ -39,10 +50,10 @@ class TestConfigManagerData(unittest.TestCase):
self.writable_data_path + os.sep + "b10-config.db")
def test_read_from_file(self):
- ConfigManagerData.read_from_file(self.writable_data_path)
+ ConfigManagerData.read_from_file(self.writable_data_path, "b10-config.db")
self.assertRaises(ConfigManagerDataEmpty,
ConfigManagerData.read_from_file,
- "doesnotexist")
+ "doesnotexist", "b10-config.db")
self.assertRaises(ConfigManagerDataReadError,
ConfigManagerData.read_from_file,
self.data_path, "b10-config-bad1.db")
@@ -68,8 +79,8 @@ class TestConfigManagerData(unittest.TestCase):
# by equality of the .data element. If data_path or db_filename
# are different, but the contents are the same, it's still
# considered equal
- cfd1 = ConfigManagerData(self.data_path)
- cfd2 = ConfigManagerData(self.data_path)
+ cfd1 = ConfigManagerData(self.data_path, file_name="b10-config.db")
+ cfd2 = ConfigManagerData(self.data_path, file_name="b10-config.db")
self.assertEqual(cfd1, cfd2)
cfd2.data_path = "some/unknown/path"
self.assertEqual(cfd1, cfd2)
@@ -85,10 +96,25 @@ class TestConfigManager(unittest.TestCase):
self.data_path = os.environ['CONFIG_TESTDATA_PATH']
self.writable_data_path = os.environ['CONFIG_WR_TESTDATA_PATH']
self.fake_session = FakeModuleCCSession()
- self.cm = ConfigManager(self.writable_data_path, self.fake_session)
+ self.cm = ConfigManager(self.writable_data_path,
+ database_filename="b10-config.db",
+ session=self.fake_session)
self.name = "TestModule"
self.spec = isc.config.module_spec_from_file(self.data_path + os.sep + "/spec2.spec")
-
+
+ def test_paths(self):
+ """
+ Test data_path and database filename is passed trough to
+ underlying ConfigManagerData.
+ """
+ cm = ConfigManager("datapath", "filename", self.fake_session)
+ self.assertEqual("datapath" + os.sep + "filename",
+ cm.config.db_filename)
+ # It should preserve it while reading
+ cm.read_config()
+ self.assertEqual("datapath" + os.sep + "filename",
+ cm.config.db_filename)
+
def test_init(self):
self.assert_(self.cm.module_specs == {})
self.assert_(self.cm.data_path == self.writable_data_path)
diff --git a/src/lib/python/isc/config/tests/config_data_test.py b/src/lib/python/isc/config/tests/config_data_test.py
index 1aded94..923e0b6 100644
--- a/src/lib/python/isc/config/tests/config_data_test.py
+++ b/src/lib/python/isc/config/tests/config_data_test.py
@@ -465,8 +465,8 @@ class TestMultiConfigData(unittest.TestCase):
module_spec = isc.config.module_spec_from_file(self.data_path + os.sep + "spec24.spec")
self.mcd.set_specification(module_spec)
- maps = self.mcd.get_value_maps("/Spec24/item")
- self.assertEqual([], maps)
+ self.assertRaises(isc.cc.data.DataNotFoundError,
+ self.mcd.get_value_maps, "/Spec24/item", 4)
self.mcd._set_current_config({ "Spec24": { "item": [] } })
maps = self.mcd.get_value_maps("/Spec24/item")
self.assertEqual([{'default': False, 'modified': False, 'name': 'Spec24/item', 'type': 'list', 'value': []}], maps)
diff --git a/src/lib/python/isc/config/tests/unittest_fakesession.py b/src/lib/python/isc/config/tests/unittest_fakesession.py
index e31b436..1641ec0 100644
--- a/src/lib/python/isc/config/tests/unittest_fakesession.py
+++ b/src/lib/python/isc/config/tests/unittest_fakesession.py
@@ -35,6 +35,7 @@ class FakeModuleCCSession:
# the message_queue is empty when receive is called, throw
# a SessionTimeout
self._timeout = 0
+ self._closed = False
def group_subscribe(self, group_name, instance_name = None):
if not group_name in self.subscriptions:
@@ -43,6 +44,11 @@ class FakeModuleCCSession:
self.subscriptions[group_name].append(instance_name)
def group_unsubscribe(self, group_name, instance_name = None):
+
+ # raises SessionError if the session has been already closed.
+ if self._closed:
+ raise isc.cc.SessionError("Session has been closed.")
+
if group_name in self.subscriptions:
if instance_name:
if len(self.subscriptions[group_name]) > 1:
@@ -94,7 +100,8 @@ class FakeModuleCCSession:
def close(self):
# need to pass along somehow that this function has been called,
- self._socket = "closed"
+ self._socket = None
+ self._closed = True
def set_timeout(self, timeout):
self._timeout = timeout
diff --git a/src/lib/python/isc/net/parse.py b/src/lib/python/isc/net/parse.py
index 86d95aa..30edadc 100644
--- a/src/lib/python/isc/net/parse.py
+++ b/src/lib/python/isc/net/parse.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 CZ NIC
+# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/python/isc/net/tests/parse_test.py b/src/lib/python/isc/net/tests/parse_test.py
index 53fca16..ba97da6 100644
--- a/src/lib/python/isc/net/tests/parse_test.py
+++ b/src/lib/python/isc/net/tests/parse_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 CZ NIC
+# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/python/isc/testutils/Makefile.am b/src/lib/python/isc/testutils/Makefile.am
new file mode 100644
index 0000000..8f00a9b
--- /dev/null
+++ b/src/lib/python/isc/testutils/Makefile.am
@@ -0,0 +1 @@
+EXTRA_DIST = __init__.py parse_args.py
diff --git a/src/lib/python/isc/testutils/README b/src/lib/python/isc/testutils/README
new file mode 100644
index 0000000..c0389a8
--- /dev/null
+++ b/src/lib/python/isc/testutils/README
@@ -0,0 +1,3 @@
+This contains some shared test code for other modules and python processes.
+That's why it doesn't have its own test subdirectory and why it isn't
+installed.
diff --git a/src/lib/python/isc/testutils/__init__.py b/src/lib/python/isc/testutils/__init__.py
new file mode 100644
index 0000000..afcccf4
--- /dev/null
+++ b/src/lib/python/isc/testutils/__init__.py
@@ -0,0 +1,17 @@
+# Copyright (C) 2011 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.
+
+# Nothing here, really, it's just to tell python this directory is in
+# module hierarchy
diff --git a/src/lib/python/isc/testutils/parse_args.py b/src/lib/python/isc/testutils/parse_args.py
new file mode 100644
index 0000000..5d79137
--- /dev/null
+++ b/src/lib/python/isc/testutils/parse_args.py
@@ -0,0 +1,30 @@
+# Copyright (C) 2011 Internet Systems Consortium.
+#
+# Permission to use, copy, modify, and distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SYSTEMS CONSORTIUM
+# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
+# INTERNET SYSTEMS CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
+# FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+from optparse import OptionParser
+
+class OptsError(Exception):
+ """To know when OptionParser would exit"""
+ pass
+
+class TestOptParser(OptionParser):
+ """
+ We define our own option parser to push into the parsing routine.
+ This one does not exit the whole application on error, it just raises
+ exception. It doesn't change anything else. The application uses the
+ stock one.
+ """
+ def error(self, message):
+ raise OptsError(message)
diff --git a/src/lib/python/isc/util/process.py b/src/lib/python/isc/util/process.py
index 25775af..84a2259 100644
--- a/src/lib/python/isc/util/process.py
+++ b/src/lib/python/isc/util/process.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 CZ NIC
+# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/python/isc/util/tests/process_test.py b/src/lib/python/isc/util/tests/process_test.py
index ffe8371..5005aa0 100644
--- a/src/lib/python/isc/util/tests/process_test.py
+++ b/src/lib/python/isc/util/tests/process_test.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2010 CZ NIC
+# Copyright (C) 2010 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
diff --git a/src/lib/rbmsgq/lib/cc.rb b/src/lib/rbmsgq/lib/cc.rb
deleted file mode 100644
index b16f1ac..0000000
--- a/src/lib/rbmsgq/lib/cc.rb
+++ /dev/null
@@ -1,60 +0,0 @@
-# Copyright (C) 2009 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.
-
-unless respond_to?('relative_feature') # nodoc
- def require_relative(relative_feature)
- c = caller.first
- fail "Can't parse #{c}" unless c.rindex(/:\d+(:in `.*')?$/)
- file = $`
- if /\A\((.*)\)/ =~ file # eval, etc.
- raise LoadError, "require_relative is called in #{$1}"
- end
- absolute = File.expand_path(relative_feature, File.dirname(file))
- require absolute
- end
-end
-
-class CC
- def self.set_utf8(str) #nodoc
- if str.respond_to?('force_encoding')
- str.force_encoding(Encoding::UTF_8)
- end
- end
-
- def self.set_binary(str) #nodoc
- if str.respond_to?('force_encoding')
- str.force_encoding(Encoding::BINARY)
- end
- end
-end
-
-require_relative 'cc/message'
-require_relative 'cc/session'
-
-if $0 == __FILE__
- cc = CC::Session.new
-
- puts "Our local name: #{cc.lname}"
-
- cc.group_subscribe("test")
-
- counter = 0
-
- while counter < 10000 do
- cc.group_sendmsg({ :counter => counter }, "test", "foo")
- routing, data = cc.group_recvmsg(false)
- counter += 1
- end
-end
diff --git a/src/lib/rbmsgq/lib/cc/message.rb b/src/lib/rbmsgq/lib/cc/message.rb
deleted file mode 100644
index 254caf0..0000000
--- a/src/lib/rbmsgq/lib/cc/message.rb
+++ /dev/null
@@ -1,324 +0,0 @@
-# Copyright (C) 2009 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.
-
-class CC
- class DecodeError < Exception ; end
-end
-
-class CC
-class Message
- PROTOCOL_VERSION = 0x536b616e
-
- ITEM_BLOB = 0x01
- ITEM_HASH = 0x02
- ITEM_LIST = 0x03
- ITEM_NULL = 0x04
- ITEM_BOOL = 0x05
- ITEM_INT = 0x06
- ITEM_UTF8 = 0x08
- ITEM_MASK = 0x0f
-
- ITEM_LENGTH_32 = 0x00
- ITEM_LENGTH_16 = 0x10
- ITEM_LENGTH_8 = 0x20
- ITEM_LENGTH_MASK = 0x30
-
- def initialize(msg = nil)
- @data = [PROTOCOL_VERSION].pack("N")
- if msg.is_a?(Hash)
- @data += CC::Message::encode_hash(msg)
- elsif msg.is_a?(String)
- @data = msg
- else
- raise ArgumentError, "initializer is not a Hash or String"
- end
- end
-
- def to_wire
- CC::set_binary(@data)
- @data
- end
-
- #
- # Encode a message. The item passed in should be a hash, and can contain
- # any number of items of any type afterwards. All keys in the hash must
- # be of type String or Symbol, and the values may be of any type. If
- # the value is a Hash or Array, it will be encoded as a message type
- # HASH or LIST. If it is nil, it will be encoded as NULL, and if it is
- # any other type, its to_s method will be called on it and it will be
- # encoded as a UTF8 item.
- #
- def self.to_wire(msg)
- encoded = [PROTOCOL_VERSION].pack("N")
- encoded += encode_hash(msg)
- encoded.force_encoding('binary')
- encoded
- end
-
- #
- # Decode a wire format message.
- #
- def self.from_wire(msg)
- if msg.length < 4
- raise CC::DecodeError, "Data is too short to decode"
- end
- msg.force_encoding('binary')
- version, msg = msg.unpack("N a*")
- unless version == PROTOCOL_VERSION
- raise CC::DecodeError, "Incorrect protocol version"
- end
- decode_hash(msg)
- end
-
- private
- # encode a simple string with a length prefix
- def self.encode_tag(tag)
- tag = tag.to_s
- [tag.length, tag].pack("C/a*")
- end
-
- def self.encode_length_and_type(data, type)
- if data.nil?
- [ITEM_NULL].pack("C")
- else
- len = data.length
- if len < 0x00000100
- [type | ITEM_LENGTH_8, len, data].pack("C C/a*")
- elsif len < 0x00010000
- [type | ITEM_LENGTH_16, len, data].pack("C n/a*")
- else
- [type | ITEM_LENGTH_32, len, data].pack("C N/a*")
- end
- end
- end
-
- # pack a string, including its type and length.
- def self.pack_utf8(str)
- encode_length_and_type(str.to_s.encode('binary'), ITEM_UTF8)
- end
-
- def self.pack_bool(bool)
- encode_length_and_type(encode_bool(bool), ITEM_BOOL)
- end
-
- def self.pack_int(int)
- encode_length_and_type(encode_int(int), ITEM_INT)
- end
-
- def self.pack_blob(str)
- encode_length_and_type(str.to_s, ITEM_BLOB)
- end
-
- def self.pack_array(arr)
- encode_length_and_type(encode_array(arr), ITEM_LIST)
- end
-
- def self.pack_hash(hash)
- encode_length_and_type(encode_hash(hash), ITEM_HASH)
- end
-
- def self.encode_data(data)
- str.to_s
- end
-
- def self.encode_utf8(str)
- str.to_s.encode('binary')
- end
-
- def self.pack_nil
- encode_length_and_type(nil, ITEM_NULL)
- end
-
- def self.encode_item(item)
- case item
- when nil
- ret = pack_nil
- when Hash
- ret = pack_hash(item)
- when Array
- ret = pack_array(item)
- when String
- if item.encoding == 'utf-8'
- ret = pack_utf8(item)
- else
- ret = pack_blob(item)
- end
- when FalseClass
- ret = pack_bool(item)
- when TrueClass
- ret = pack_bool(item)
- when Integer
- ret = pack_int(item)
- else
- ret = pack_blob(item.to_s)
- end
-
- ret
- end
-
- def self.encode_hash(msg)
- unless msg.is_a?Hash
- raise ArgumentError, "Should be a hash"
- end
- buffer = ""
- msg.each do |key, value|
- buffer += encode_tag(key)
- buffer += encode_item(value)
- end
- buffer
- end
-
- def self.encode_bool(msg)
- unless msg.class == FalseClass or msg.class == TrueClass
- raise ArgumentError, "Should be true or false"
- end
- if msg
- [0x01].pack("C")
- else
- [0x00].pack("C")
- end
- end
-
- def self.encode_int(int)
- int.to_s.encode('binary')
- end
-
- def self.encode_array(msg)
- unless msg.is_a?Array
- raise ArgumentError, "Should be an array"
- end
- buffer = ""
- msg.each do |value|
- buffer += encode_item(value)
- end
- buffer
- end
-
- def self.decode_tag(str)
- if str.length < 1
- raise CC::DecodeError, "Data underrun while decoding"
- end
- length = str.unpack("C")[0]
- if str.length - 1 < length
- raise CC::DecodeError, "Data underrun while decoding"
- end
- tag, remainder = str.unpack("x a#{length} a*")
- [ tag.encode('utf-8'), remainder ]
- end
-
- def self.decode_item(msg)
- if msg.length < 1
- raise CC::DecodeError, "Data underrun while decoding"
- end
- type_and_length_format = msg.unpack("C")[0]
- type = type_and_length_format & ITEM_MASK
- length_format = type_and_length_format & ITEM_LENGTH_MASK
-
- if type == ITEM_NULL
- msg = msg.unpack("x a*")[0]
- else
- if length_format == ITEM_LENGTH_8
- if msg.length - 1 < 1
- raise CC::DecodeError, "Data underrun while decoding"
- end
- length, msg = msg.unpack("x C a*")
- elsif length_format == ITEM_LENGTH_16
- if msg.length - 1 < 2
- raise CC::DecodeError, "Data underrun while decoding"
- end
- length, msg = msg.unpack("x n a*")
- elsif length_format == ITEM_LENGTH_32
- if msg.length - 1 < 4
- raise CC::DecodeError, "Data underrun while decoding"
- end
- length, msg = msg.unpack("x N a*")
- end
- if msg.length < length
- raise CC::DecodeError, "Data underrun while decoding"
- end
- item, msg = msg.unpack("a#{length} a*")
- end
-
- # unpack item based on type
- case type
- when ITEM_BLOB
- value = item
- when ITEM_UTF8
- value = item.encode('utf-8')
- when ITEM_BOOL
- value = decode_bool(item)
- when ITEM_INT
- value = decode_int(item)
- when ITEM_HASH
- value = decode_hash(item)
- when ITEM_LIST
- value = decode_array(item)
- when ITEM_NULL
- value = nil
- else
- raise CC::DecodeError, "Unknown item type in decode: #{type}"
- end
-
- [value, msg]
- end
-
- def self.decode_bool(msg)
- return msg == [0x01].pack("C")
- end
-
- def self.decode_int(msg)
- return Integer(msg.encode('utf-8'))
- end
-
- def self.decode_hash(msg)
- ret = {}
- while msg.length > 0
- tag, msg = decode_tag(msg)
- value, msg = decode_item(msg)
- ret[tag] = value
- end
-
- ret
- end
-
- def self.decode_array(msg)
- ret = []
- while msg.length > 0
- value, msg = decode_item(msg)
- ret << value
- end
-
- ret
- end
-
-end # class Message
-end # class CC
-
-if $0 == __FILE__
- target = {
- "from" => "sender at host",
- "to" => "recipient at host",
- "seq" => 1234,
- "data" => {
- "list" => [ 1, 2, nil, true, false, "this" ],
- "description" => "Fun for all",
- },
- }
-
- wire = CC::Message.to_wire(target)
- puts wire.inspect
-
- puts CC::Message.from_wire(wire).inspect
-end
diff --git a/src/lib/rbmsgq/lib/cc/session.rb b/src/lib/rbmsgq/lib/cc/session.rb
deleted file mode 100644
index 1d9cef5..0000000
--- a/src/lib/rbmsgq/lib/cc/session.rb
+++ /dev/null
@@ -1,214 +0,0 @@
-# Copyright (C) 2009 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.
-
-require 'socket'
-
-class CC
-class ProtocolError < Exception ; end
-end
-
-class CC
-class Session
- attr_reader :socket
- attr_reader :lname
-
- #
- # :host => host to connect to (defaults to "127.0.0.1")
- # :port => port to connect to (defaults to 9913)
- #
- def initialize(args = {})
- @socket = nil # TCP socket.
- @lname = nil # local name, or nil if not connected.
- @recvbuffer = "" # data buffer for partial reads.
- @recvlength = nil # if non-nil, we have a length to fill buffer to.
- @sendbuffer = "" # pending output data.
- @sequence = "a" # per message sequence id, always unique
-
- options = {
- :host => "127.0.0.1",
- :port => 9912
- }.merge(args)
-
- @socket = TCPSocket.new(options[:host], options[:port])
-
- #
- # Get our local name.
- #
- sendmsg({ :type => :getlname })
- msg = recvmsg(false)
- @lname = msg["lname"]
- if @lname.nil?
- raise CC::ProtocolError, "Could not get local name"
- end
- CC::set_utf8(@lname)
- end
-
- #
- # Send a message to the controller. The item to send can either be a
- # CC::Message object, or a Hash. If a Hash, it will be internally
- # converted to a CC::Message before transmitting.
- #
- # A return value of true means the entire message was not
- # transmitted, and a call to send_pending will have to be
- # made to send remaining data. This should only happen when
- # the socket is in non-blocking mode.
- #
- def sendmsg(msg)
- if msg.is_a?(Hash)
- msg = CC::Message.new(msg)
- end
-
- unless msg.is_a?(CC::Message)
- raise ArgumentError, "msg is not a CC::Message or a Hash"
- end
-
- wire = msg.to_wire
- @sendbuffer << [wire.length].pack("N")
- @sendbuffer << wire
-
- send_pending
- end
-
- #
- # Send as much data as we can.
- def send_pending
- return false if @sendbuffer.length == 0
- sent = @socket.send(@sendbuffer, 0)
- @sendbuffer = @sendbuffer[sent .. -1]
- @sendbuffer.length == 0 ? true : false
- end
-
- def recvmsg(nonblock = true)
- data = receive_full_buffer(nonblock)
- if data
- CC::Message::from_wire(data)
- else
- nil
- end
- end
-
- def group_subscribe(group, instance = "*", subtype = "normal")
- sendmsg({ :type => "subscribe",
- :group => group,
- :instance => instance,
- :subtype => subtype,
- })
- end
-
- def group_unsubscribe(group, instance = "*")
- sendmsg({ :type => "unsubscribe",
- :group => group,
- :instance => instance,
- })
- end
-
- def group_sendmsg(msg, group, instance = "*", to = "*")
- seq = next_sequence
- sendmsg({ :type => "send",
- :from => @lname,
- :to => to,
- :group => group,
- :instance => instance,
- :seq => seq,
- :msg => CC::Message.to_wire(msg),
- })
- seq
- end
-
- def group_replymsg(routing, msg)
- seq = next_sequence
- sendmsg({ :type => "send",
- :from => @lname,
- :to => routing["from"],
- :group => routing["group"],
- :instance => routing["instance"],
- :seq => seq,
- :reply => routing["seq"],
- :msg => CC::Message.to_wire(msg),
- })
- seq
- end
-
- def group_recvmsg(nonblock = true)
- msg = recvmsg(nonblock)
- return nil if msg.nil?
- data = CC::Message::from_wire(msg["msg"])
- msg.delete("msg")
- return [data, msg]
- end
-
- private
-
- def next_sequence
- @sequence.next!
- end
-
- #
- # A rather tricky function. If we have something waiting in our buffer,
- # and it will satisfy the current request, we will read it all in. If
- # not, we will read only what we need to satisfy a single message.
- #
- def receive_full_buffer(nonblock)
- # read the length prefix if we need it still.
- if @recvlength.nil?
- length = 4
- length -= @recvbuffer.length
- data = nil
- begin
- if nonblock
- data = @socket.recv_nonblock(length)
- else
- data = @socket.recv(length)
- end
- rescue Errno::EINPROGRESS
- rescue Errno::EAGAIN
- end
- return nil if data == nil
- @recvbuffer += data
- return nil if @recvbuffer.length < 4
- @recvlength = @recvbuffer.unpack('N')[0]
- @recvbuffer = ""
- CC::set_binary(@recvbuffer)
- end
-
- #
- # we have a length target. Loop reading data until we get enough to
- # fill our buffer.
- #
- length = @recvlength - @recvbuffer.length
- while (length > 0) do
- data = nil
- begin
- if nonblock
- data = @socket.recv_nonblock(length)
- else
- data = @socket.recv(length)
- end
- rescue Errno::EINPROGRESS
- rescue Errno::EAGAIN
- end
- return nil if data == 0 # blocking I/O
- @recvbuffer += data
- length -= data.length
- end
-
- data = @recvbuffer
- @recvbuffer = ""
- @recvlength = nil
- data
- end
-
-end # class Session
-end # class CC
diff --git a/src/lib/resolve/Makefile.am b/src/lib/resolve/Makefile.am
index c9f1326..0b29da4 100644
--- a/src/lib/resolve/Makefile.am
+++ b/src/lib/resolve/Makefile.am
@@ -14,5 +14,16 @@ libresolve_la_SOURCES = resolve.h resolve.cc
libresolve_la_SOURCES += resolver_interface.h
libresolve_la_SOURCES += resolver_callback.h resolver_callback.cc
libresolve_la_SOURCES += response_classifier.cc response_classifier.h
+libresolve_la_SOURCES += recursive_query.cc recursive_query.h
libresolve_la_LIBADD = $(top_builddir)/src/lib/dns/libdns++.la
libresolve_la_LIBADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+
+# Note: the ordering matters: -Wno-... must follow -Wextra (defined in
+# B10_CXXFLAGS)
+libresolve_la_CXXFLAGS = $(AM_CXXFLAGS)
+if USE_CLANGPP
+# For clang++, we need to turn off -Werror completely.
+libresolve_la_CXXFLAGS += -Wno-error
+endif
+libresolve_la_CPPFLAGS = $(AM_CPPFLAGS)
+
diff --git a/src/lib/resolve/recursive_query.cc b/src/lib/resolve/recursive_query.cc
new file mode 100644
index 0000000..d100209
--- /dev/null
+++ b/src/lib/resolve/recursive_query.cc
@@ -0,0 +1,944 @@
+// 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 <netinet/in.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <unistd.h> // for some IPC/network system calls
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+
+#include <config.h>
+
+#include <log/dummylog.h>
+
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/opcode.h>
+#include <dns/exceptions.h>
+
+#include <resolve/resolve.h>
+#include <cache/resolver_cache.h>
+#include <nsas/address_request_callback.h>
+#include <nsas/nameserver_address.h>
+
+#include <asio.hpp>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+
+using isc::log::dlog;
+using namespace isc::dns;
+
+namespace asiolink {
+
+typedef std::vector<std::pair<std::string, uint16_t> > AddressVector;
+
+// Here we do not use the typedef above, as the SunStudio compiler
+// mishandles this in its name mangling, and wouldn't compile.
+// We can probably use a typedef, but need to move it to a central
+// location and use it consistently.
+RecursiveQuery::RecursiveQuery(DNSService& dns_service,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream,
+ const std::vector<std::pair<std::string, uint16_t> >& upstream_root,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries) :
+ dns_service_(dns_service),
+ nsas_(nsas), cache_(cache),
+ upstream_(new AddressVector(upstream)),
+ upstream_root_(new AddressVector(upstream_root)),
+ test_server_("", 0),
+ query_timeout_(query_timeout), client_timeout_(client_timeout),
+ lookup_timeout_(lookup_timeout), retries_(retries)
+{
+}
+
+// Set the test server - only used for unit testing.
+
+void
+RecursiveQuery::setTestServer(const std::string& address, uint16_t port) {
+ dlog("Setting test server to " + address + "(" +
+ boost::lexical_cast<std::string>(port) + ")");
+ test_server_.first = address;
+ test_server_.second = port;
+}
+
+
+namespace {
+
+typedef std::pair<std::string, uint16_t> addr_t;
+
+/*
+ * This is a query in progress. When a new query is made, this one holds
+ * the context information about it, like how many times we are allowed
+ * to retry on failure, what to do when we succeed, etc.
+ *
+ * Used by RecursiveQuery::sendQuery.
+ */
+class RunningQuery : public IOFetch::Callback {
+
+class ResolverNSASCallback : public isc::nsas::AddressRequestCallback {
+public:
+ ResolverNSASCallback(RunningQuery* rq) : rq_(rq) {}
+
+ void success(const isc::nsas::NameserverAddress& address) {
+ dlog("Found a nameserver, sending query to " + address.getAddress().toText());
+ rq_->nsasCallbackCalled();
+ rq_->sendTo(address);
+ }
+
+ void unreachable() {
+ dlog("Nameservers unreachable");
+ // Drop query or send servfail?
+ rq_->nsasCallbackCalled();
+ rq_->makeSERVFAIL();
+ rq_->callCallback(true);
+ rq_->stop();
+ }
+
+private:
+ RunningQuery* rq_;
+};
+
+
+private:
+ // The io service to handle async calls
+ IOService& io_;
+
+ // Info for (re)sending the query (the question and destination)
+ Question question_;
+
+ // This is the query message got from client
+ ConstMessagePtr query_message_;
+
+ // This is where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // Test server - only used for testing. This takes precedence over all
+ // other servers if the port is non-zero.
+ std::pair<std::string, uint16_t> test_server_;
+
+ // Buffer to store the intermediate results.
+ OutputBufferPtr buffer_;
+
+ // The callback will be called when we have either decided we
+ // are done, or when we give up
+ isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+ // Protocol used for the last query. This is set to IOFetch::UDP when a
+ // new upstream query is initiated, and changed to IOFetch::TCP if a
+ // packet is returned with the TC bit set. It is stored here to detect the
+ // case of a TCP packet being returned with the TC bit set.
+ IOFetch::Protocol protocol_;
+
+ // To prevent both unreasonably long cname chains and cname loops,
+ // we simply keep a counter of the number of CNAMEs we have
+ // followed so far (and error if it exceeds RESOLVER_MAX_CNAME_CHAIN
+ // from lib/resolve/response_classifier.h)
+ unsigned cname_count_;
+
+ /*
+ * TODO Do something more clever with timeouts. In the long term, some
+ * computation of average RTT, increase with each retry, etc.
+ */
+ // Timeout information for outgoing queries
+ int query_timeout_;
+ unsigned retries_;
+
+ // normal query state
+
+ // Update the question that will be sent to the server
+ void setQuestion(const Question& new_question) {
+ question_ = new_question;
+ }
+
+ // TODO: replace by our wrapper
+ asio::deadline_timer client_timer;
+ asio::deadline_timer lookup_timer;
+
+ // If we timed out ourselves (lookup timeout), stop issuing queries
+ bool done_;
+
+ // If we have a client timeout, we call back with a failure message,
+ // but we do not stop yet. We use this variable to make sure we
+ // don't call back a second time later
+ bool callback_called_;
+
+ // Reference to our NSAS
+ isc::nsas::NameserverAddressStore& nsas_;
+
+ // Reference to our cache
+ isc::cache::ResolverCache& cache_;
+
+ // the 'current' zone we are in (i.e.) we start out at the root,
+ // and for each delegation this gets updated with the zone the
+ // delegation points to.
+ // TODO: make this a Name (it is a string right now because most
+ // of the call we use it in take a string, we need update those
+ // too).
+ std::string cur_zone_;
+
+ // This is the handler we pass on to the NSAS; it is called when
+ // the NSAS has an address for us to query
+ boost::shared_ptr<ResolverNSASCallback> nsas_callback_;
+
+ // this is set to true if we have asked the nsas to give us
+ // an address and we are waiting for it to call us back.
+ // We use is to cancel the outstanding callback in case we
+ // have a lookup timeout and decide to give up
+ bool nsas_callback_out_;
+
+ // This is the nameserver we have an outstanding query to.
+ // It is used to update the RTT once the query returns
+ isc::nsas::NameserverAddress current_ns_address;
+
+ // The moment in time we sent a query to the nameserver above.
+ struct timeval current_ns_qsent_time;
+
+ // RunningQuery deletes itself when it is done. In order for us
+ // to do this safely, we must make sure that there are no events
+ // that might call back to it. There are two types of events in
+ // this sense; the timers we set ourselves (lookup and client),
+ // and outstanding queries to nameservers. When each of these is
+ // started, we increase this value. When they fire, it is decreased
+ // again. We cannot delete ourselves until this value is back to 0.
+ //
+ // Note that the NSAS callback is *not* seen as an outstanding
+ // event; we can cancel the NSAS callback safely.
+ size_t outstanding_events_;
+
+ // perform a single lookup; first we check the cache to see
+ // if we have a response for our query stored already. if
+ // so, call handlerecursiveresponse(), if not, we call send()
+ void doLookup() {
+ cur_zone_ = ".";
+ dlog("doLookup: try cache");
+ Message cached_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question_, cached_message);
+ if (cache_.lookup(question_.getName(), question_.getType(),
+ question_.getClass(), cached_message)) {
+ dlog("Message found in cache, continuing with that");
+ // Should these be set by the cache too?
+ cached_message.setOpcode(Opcode::QUERY());
+ cached_message.setRcode(Rcode::NOERROR());
+ cached_message.setHeaderFlag(Message::HEADERFLAG_QR);
+ if (handleRecursiveAnswer(cached_message)) {
+ callCallback(true);
+ stop();
+ }
+ } else {
+ send();
+ }
+
+ }
+
+ // Send the current question to the given nameserver address
+ void sendTo(const isc::nsas::NameserverAddress& address) {
+ // We need to keep track of the Address, so that we can update
+ // the RTT
+ current_ns_address = address;
+ gettimeofday(¤t_ns_qsent_time, NULL);
+ ++outstanding_events_;
+ if (test_server_.second != 0) {
+ IOFetch query(protocol_, io_, question_,
+ test_server_.first,
+ test_server_.second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else {
+ IOFetch query(protocol_, io_, question_,
+ current_ns_address.getAddress(),
+ 53, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ }
+ }
+
+ // 'general' send, ask the NSAS to give us an address.
+ void send(IOFetch::Protocol protocol = IOFetch::UDP) {
+ protocol_ = protocol; // Store protocol being used for this
+ if (test_server_.second != 0) {
+ dlog("Sending upstream query (" + question_.toText() +
+ ") to test server at " + test_server_.first);
+ gettimeofday(¤t_ns_qsent_time, NULL);
+ ++outstanding_events_;
+ IOFetch query(protocol, io_, question_,
+ test_server_.first,
+ test_server_.second, buffer_, this,
+ query_timeout_);
+ io_.get_io_service().post(query);
+ } else {
+ // Ask the NSAS for an address for the current zone,
+ // the callback will call the actual sendTo()
+ dlog("Look up nameserver for " + cur_zone_ + " in NSAS");
+ // Can we have multiple calls to nsas_out? Let's assume not
+ // for now
+ assert(!nsas_callback_out_);
+ nsas_callback_out_ = true;
+ nsas_.lookup(cur_zone_, question_.getClass(), nsas_callback_);
+ }
+ }
+
+ // Called by our NSAS callback handler so we know we do not have
+ // an outstanding NSAS call anymore.
+ void nsasCallbackCalled() {
+ nsas_callback_out_ = false;
+ }
+
+ // This function is called by operator() and lookup();
+ // We have an answer either from a nameserver or the cache, and
+ // we do not know yet if this is a final answer we can send back or
+ // that more recursive processing needs to be done.
+ // Depending on the content, we go on recursing or return
+ //
+ // This method also updates the cache, depending on the content
+ // of the message
+ //
+ // returns true if we are done (either we have an answer or an
+ // error message)
+ // returns false if we are not done
+ bool handleRecursiveAnswer(const Message& incoming) {
+ dlog("Handle response");
+ // In case we get a CNAME, we store the target
+ // here (classify() will set it when it walks through
+ // the cname chain to verify it).
+ Name cname_target(question_.getName());
+
+ isc::resolve::ResponseClassifier::Category category =
+ isc::resolve::ResponseClassifier::classify(
+ question_, incoming, cname_target, cname_count_);
+
+ bool found_ns = false;
+
+ switch (category) {
+ case isc::resolve::ResponseClassifier::ANSWER:
+ case isc::resolve::ResponseClassifier::ANSWERCNAME:
+ // Done. copy and return.
+ dlog("Response is an answer");
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::CNAME:
+ dlog("Response is CNAME!");
+ // (unfinished) CNAME. We set our question_ to the CNAME
+ // target, then start over at the beginning (for now, that
+ // is, we reset our 'current servers' to the root servers).
+ if (cname_count_ >= RESOLVER_MAX_CNAME_CHAIN) {
+ // just give up
+ dlog("CNAME chain too long");
+ makeSERVFAIL();
+ return true;
+ }
+
+ answer_message_->appendSection(Message::SECTION_ANSWER,
+ incoming);
+
+ question_ = Question(cname_target, question_.getClass(),
+ question_.getType());
+
+ dlog("Following CNAME chain to " + question_.toText());
+ doLookup();
+ return false;
+ break;
+ case isc::resolve::ResponseClassifier::NXDOMAIN:
+ case isc::resolve::ResponseClassifier::NXRRSET:
+ dlog("Response is NXDOMAIN or NXRRSET");
+ // NXDOMAIN, just copy and return.
+ dlog(incoming.toText());
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ // no negcache yet
+ //cache_.update(*answer_message_);
+ return true;
+ break;
+ case isc::resolve::ResponseClassifier::REFERRAL:
+ dlog("Response is referral");
+ cache_.update(incoming);
+ // Referral. For now we just take the first glue address
+ // we find and continue with that
+
+ // auth section should have at least one RRset
+ // and one of them should be an NS (otherwise
+ // classifier should have error'd) to a subdomain
+ for (RRsetIterator rrsi = incoming.beginSection(Message::SECTION_AUTHORITY);
+ rrsi != incoming.endSection(Message::SECTION_AUTHORITY) && !found_ns;
+ ++rrsi) {
+ ConstRRsetPtr rrs = *rrsi;
+ if (rrs->getType() == RRType::NS()) {
+ NameComparisonResult compare(Name(cur_zone_).compare(rrs->getName()));
+ if (compare.getRelation() == NameComparisonResult::SUPERDOMAIN) {
+ // TODO: make cur_zone_ a Name instead of a string
+ // (this requires a few API changes in related
+ // libraries, so as not to need many conversions)
+ cur_zone_ = rrs->getName().toText();
+ dlog("Referred to zone " + cur_zone_);
+ found_ns = true;
+ break;
+ }
+ }
+ }
+
+ if (found_ns) {
+ // next resolver round
+ // we do NOT use doLookup() here, but send() (i.e. we
+ // skip the cache), since if we had the final answer
+ // instead of a delegation cached, we would have been
+ // there by now.
+ GlueHints glue_hints(cur_zone_, incoming);
+
+ // Ask the NSAS for an address, or glue.
+ // This will eventually result in either sendTo()
+ // or stop() being called by nsas_callback_
+ assert(!nsas_callback_out_);
+ nsas_callback_out_ = true;
+ nsas_.lookup(cur_zone_, question_.getClass(),
+ nsas_callback_, ANY_OK, glue_hints);
+ return false;
+ } else {
+ dlog("No NS RRset in referral?");
+ // TODO this will result in answering with the delegation. oh well
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ return true;
+ }
+ break;
+ case isc::resolve::ResponseClassifier::TRUNCATED:
+ // Truncated packet. If the protocol we used for the last one is
+ // UDP, re-query using TCP. Otherwise regard it as an error.
+ if (protocol_ == IOFetch::UDP) {
+ dlog("Response truncated, re-querying over TCP");
+ send(IOFetch::TCP);
+ return false;
+ }
+ // Was a TCP query so we have received a packet over TCP with the TC
+ // bit set: drop through to common error processing.
+ // TODO: Can we use what we have received instead of discarding it?
+
+ case isc::resolve::ResponseClassifier::EMPTY:
+ case isc::resolve::ResponseClassifier::EXTRADATA:
+ case isc::resolve::ResponseClassifier::INVNAMCLASS:
+ case isc::resolve::ResponseClassifier::INVTYPE:
+ case isc::resolve::ResponseClassifier::MISMATQUEST:
+ case isc::resolve::ResponseClassifier::MULTICLASS:
+ case isc::resolve::ResponseClassifier::NOTONEQUEST:
+ case isc::resolve::ResponseClassifier::NOTRESPONSE:
+ case isc::resolve::ResponseClassifier::NOTSINGLE:
+ case isc::resolve::ResponseClassifier::OPCODE:
+ case isc::resolve::ResponseClassifier::RCODE:
+ // Should we try a different server rather than SERVFAIL?
+ makeSERVFAIL();
+ return true;
+ break;
+ }
+
+ // Since we do not have a default in the switch above,
+ // the compiler should have errored on any missing case
+ // statements.
+ assert(false);
+ return true;
+ }
+
+public:
+ RunningQuery(IOService& io,
+ const Question& question,
+ MessagePtr answer_message,
+ std::pair<std::string, uint16_t>& test_server,
+ OutputBufferPtr buffer,
+ isc::resolve::ResolverInterface::CallbackPtr cb,
+ int query_timeout, int client_timeout, int lookup_timeout,
+ unsigned retries,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache) :
+ io_(io),
+ question_(question),
+ query_message_(),
+ answer_message_(answer_message),
+ test_server_(test_server),
+ buffer_(buffer),
+ resolvercallback_(cb),
+ protocol_(IOFetch::UDP),
+ cname_count_(0),
+ query_timeout_(query_timeout),
+ retries_(retries),
+ client_timer(io.get_io_service()),
+ lookup_timer(io.get_io_service()),
+ done_(false),
+ callback_called_(false),
+ nsas_(nsas),
+ cache_(cache),
+ cur_zone_("."),
+ nsas_callback_(new ResolverNSASCallback(this)),
+ nsas_callback_out_(false),
+ outstanding_events_(0)
+ {
+ // Setup the timer to stop trying (lookup_timeout)
+ if (lookup_timeout >= 0) {
+ lookup_timer.expires_from_now(
+ boost::posix_time::milliseconds(lookup_timeout));
+ ++outstanding_events_;
+ lookup_timer.async_wait(boost::bind(&RunningQuery::lookupTimeout, this));
+ }
+
+ // Setup the timer to send an answer (client_timeout)
+ if (client_timeout >= 0) {
+ client_timer.expires_from_now(
+ boost::posix_time::milliseconds(client_timeout));
+ ++outstanding_events_;
+ client_timer.async_wait(boost::bind(&RunningQuery::clientTimeout, this));
+ }
+
+ doLookup();
+ }
+
+ // called if we have a lookup timeout; if our callback has
+ // not been called, call it now. Then stop.
+ void lookupTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ stop();
+ }
+
+ // called if we have a client timeout; if our callback has
+ // not been called, call it now. But do not stop.
+ void clientTimeout() {
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+ if (outstanding_events_ == 0) {
+ stop();
+ }
+ }
+
+ // If the callback has not been called yet, call it now
+ // If success is true, we call 'success' with our answer_message
+ // If it is false, we call failure()
+ void callCallback(bool success) {
+ if (!callback_called_) {
+ callback_called_ = true;
+
+ // There are two types of messages we could store in the
+ // cache;
+ // 1. answers to our fetches from authoritative servers,
+ // exactly as we receive them, and
+ // 2. answers to queries we received from clients, which
+ // have received additional processing (following CNAME
+ // chains, for instance)
+ //
+ // Doing only the first would mean we would have to re-do
+ // processing when we get data from our cache, and doing
+ // only the second would miss out on the side-effect of
+ // having nameserver data in our cache.
+ //
+ // So right now we do both. Since the cache (currently)
+ // stores Messages on their question section only, this
+ // does mean that we overwrite the messages we stored in
+ // the previous iteration if we are following a delegation.
+ if (success) {
+ resolvercallback_->success(answer_message_);
+ } else {
+ resolvercallback_->failure();
+ }
+ }
+ }
+
+ // We are done. If there are no more outstanding events, we delete
+ // ourselves. If there are any, we do not.
+ void stop() {
+ done_ = true;
+ if (nsas_callback_out_) {
+ nsas_.cancel(cur_zone_, question_.getClass(), nsas_callback_);
+ nsas_callback_out_ = false;
+ }
+ client_timer.cancel();
+ lookup_timer.cancel();
+ if (outstanding_events_ > 0) {
+ return;
+ } else {
+ delete this;
+ }
+ }
+
+ // This function is used as callback from DNSQuery.
+ virtual void operator()(IOFetch::Result result) {
+ // XXX is this the place for TCP retry?
+ assert(outstanding_events_ > 0);
+ --outstanding_events_;
+
+ if (!done_ && result != IOFetch::TIME_OUT) {
+ // we got an answer
+
+ // Update the NSAS with the time it took
+ struct timeval cur_time;
+ gettimeofday(&cur_time, NULL);
+ uint32_t rtt = 0;
+
+ // Only calculate RTT if it is positive
+ if (cur_time.tv_sec > current_ns_qsent_time.tv_sec ||
+ (cur_time.tv_sec == current_ns_qsent_time.tv_sec &&
+ cur_time.tv_usec > current_ns_qsent_time.tv_usec)) {
+ rtt = 1000 * (cur_time.tv_sec - current_ns_qsent_time.tv_sec);
+ rtt += (cur_time.tv_usec - current_ns_qsent_time.tv_usec) / 1000;
+ }
+
+ dlog("RTT: " + boost::lexical_cast<std::string>(rtt));
+ current_ns_address.updateRTT(rtt);
+
+ try {
+ Message incoming(Message::PARSE);
+ InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+
+ incoming.fromWire(ibuf);
+
+ buffer_->clear();
+ if (incoming.getRcode() == Rcode::NOERROR()) {
+ done_ = handleRecursiveAnswer(incoming);
+ } else {
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ done_ = true;
+ }
+ if (done_) {
+ callCallback(true);
+ stop();
+ }
+ } catch (const isc::dns::DNSProtocolError& dpe) {
+ dlog("DNS Protocol error in answer for " +
+ question_.toText() + " " +
+ question_.getType().toText() + ": " +
+ dpe.what());
+ // Right now, we treat this similar to timeouts
+ // (except we don't store RTT)
+ // We probably want to make this an integral part
+ // of the fetch data process. (TODO)
+ if (retries_--) {
+ dlog("Retrying");
+ send();
+ } else {
+ dlog("Giving up");
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ stop();
+ }
+ }
+ } else if (!done_ && retries_--) {
+ // Query timed out, but we have some retries, so send again
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", resending query");
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ send();
+ } else {
+ // We are either already done, or out of retries
+ if (result == IOFetch::TIME_OUT) {
+ dlog("Timeout for " + question_.toText() + " to " + current_ns_address.getAddress().toText() + ", giving up");
+ current_ns_address.updateRTT(isc::nsas::AddressEntry::UNREACHABLE);
+ }
+ if (!callback_called_) {
+ makeSERVFAIL();
+ callCallback(true);
+ }
+ stop();
+ }
+ }
+
+ // Clear the answer parts of answer_message, and set the rcode
+ // to servfail
+ void makeSERVFAIL() {
+ isc::resolve::makeErrorMessage(answer_message_, Rcode::SERVFAIL());
+ }
+
+};
+
+class ForwardQuery : public IOFetch::Callback {
+private:
+ // The io service to handle async calls
+ IOService& io_;
+
+ // This is the query message got from client
+ ConstMessagePtr query_message_;
+
+ // This is where we build and store our final answer
+ MessagePtr answer_message_;
+
+ // List of nameservers to forward to
+ boost::shared_ptr<AddressVector> upstream_;
+
+ // Buffer to store the result.
+ OutputBufferPtr buffer_;
+
+ // This will be notified when we succeed or fail
+ isc::resolve::ResolverInterface::CallbackPtr resolvercallback_;
+
+ /*
+ * TODO Do something more clever with timeouts. In the long term, some
+ * computation of average RTT, increase with each retry, etc.
+ */
+ // Timeout information
+ int query_timeout_;
+
+ // TODO: replace by our wrapper
+ asio::deadline_timer client_timer;
+ asio::deadline_timer lookup_timer;
+
+ // If we have a client timeout, we send back an answer, but don't
+ // stop. We use this variable to make sure we don't send another
+ // answer if we do find one later (or if we have a lookup_timeout)
+ bool answer_sent_;
+
+ // send the query to the server.
+ void send() {
+ const int uc = upstream_->size();
+ buffer_->clear();
+ int serverIndex = rand() % uc;
+ ConstQuestionPtr question = *(query_message_->beginQuestion());
+ dlog("Sending upstream query (" + question->toText() +
+ ") to " + upstream_->at(serverIndex).first);
+ // Forward the query, create the IOFetch with
+ // query message, so that query flags can be forwarded
+ // together.
+ IOFetch query(IOFetch::UDP, io_, query_message_,
+ upstream_->at(serverIndex).first,
+ upstream_->at(serverIndex).second,
+ buffer_, this, query_timeout_);
+
+ io_.get_io_service().post(query);
+ }
+
+public:
+ ForwardQuery(IOService& io,
+ ConstMessagePtr query_message,
+ MessagePtr answer_message,
+ boost::shared_ptr<AddressVector> upstream,
+ OutputBufferPtr buffer,
+ isc::resolve::ResolverInterface::CallbackPtr cb,
+ int query_timeout, int client_timeout, int lookup_timeout) :
+ io_(io),
+ query_message_(query_message),
+ answer_message_(answer_message),
+ upstream_(upstream),
+ buffer_(buffer),
+ resolvercallback_(cb),
+ query_timeout_(query_timeout),
+ client_timer(io.get_io_service()),
+ lookup_timer(io.get_io_service()),
+ answer_sent_(false)
+ {
+ // Setup the timer to stop trying (lookup_timeout)
+ if (lookup_timeout >= 0) {
+ lookup_timer.expires_from_now(
+ boost::posix_time::milliseconds(lookup_timeout));
+ lookup_timer.async_wait(boost::bind(&ForwardQuery::stop, this, false));
+ }
+
+ // Setup the timer to send an answer (client_timeout)
+ if (client_timeout >= 0) {
+ client_timer.expires_from_now(
+ boost::posix_time::milliseconds(client_timeout));
+ client_timer.async_wait(boost::bind(&ForwardQuery::clientTimeout, this));
+ }
+
+ send();
+ }
+
+ virtual void clientTimeout() {
+ // Return a SERVFAIL, but do not stop until
+ // we have an answer or timeout ourselves
+ isc::resolve::makeErrorMessage(answer_message_,
+ Rcode::SERVFAIL());
+ if (!answer_sent_) {
+ answer_sent_ = true;
+ resolvercallback_->success(answer_message_);
+ }
+ }
+
+ virtual void stop(bool resume) {
+ // if we cancel our timers, we will still get an event for
+ // that, so we cannot delete ourselves just yet (those events
+ // would be bound to a deleted object)
+ // cancel them one by one, both cancels should get us back
+ // here again.
+ // same goes if we have an outstanding query (can't delete
+ // until that one comes back to us)
+ if (resume && !answer_sent_) {
+ answer_sent_ = true;
+ resolvercallback_->success(answer_message_);
+ } else {
+ resolvercallback_->failure();
+ }
+ if (lookup_timer.cancel() != 0) {
+ return;
+ }
+ if (client_timer.cancel() != 0) {
+ return;
+ }
+
+ delete this;
+ }
+
+ // This function is used as callback from DNSQuery.
+ virtual void operator()(IOFetch::Result result) {
+ // XXX is this the place for TCP retry?
+ if (result != IOFetch::TIME_OUT) {
+ // we got an answer
+ Message incoming(Message::PARSE);
+ InputBuffer ibuf(buffer_->getData(), buffer_->getLength());
+ incoming.fromWire(ibuf);
+ isc::resolve::copyResponseMessage(incoming, answer_message_);
+ stop(true);
+ } else {
+ // timeout, give up for now
+ stop(false);
+ }
+ }
+};
+
+}
+
+void
+RecursiveQuery::resolve(const QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback)
+{
+ IOService& io = dns_service_.getIOService();
+
+ MessagePtr answer_message(new Message(Message::RENDER));
+ isc::resolve::initResponseMessage(*question, *answer_message);
+
+ OutputBufferPtr buffer(new OutputBuffer(0));
+
+ dlog("Asked to resolve: " + question->toText());
+
+ dlog("Try out cache first (direct call to resolve)");
+ // First try to see if we have something cached in the messagecache
+ if (cache_.lookup(question->getName(), question->getType(),
+ question->getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question->getName(),
+ question->getType(),
+ question->getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ callback->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, *question, answer_message,
+ test_server_, buffer, callback,
+ query_timeout_, client_timeout_,
+ lookup_timeout_, retries_, nsas_, cache_);
+ }
+ }
+}
+
+void
+RecursiveQuery::resolve(const Question& question,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ // XXX: eventually we will need to be able to determine whether
+ // the message should be sent via TCP or UDP, or sent initially via
+ // UDP and then fall back to TCP on failure, but for the moment
+ // we're only going to handle UDP.
+ IOService& io = dns_service_.getIOService();
+
+ isc::resolve::ResolverInterface::CallbackPtr crs(
+ new isc::resolve::ResolverCallbackServer(server));
+
+ // TODO: general 'prepareinitialanswer'
+ answer_message->setOpcode(isc::dns::Opcode::QUERY());
+ answer_message->addQuestion(question);
+
+ dlog("Asked to resolve: " + question.toText());
+
+ // First try to see if we have something cached in the messagecache
+ dlog("Try out cache first (started by incoming event)");
+ if (cache_.lookup(question.getName(), question.getType(),
+ question.getClass(), *answer_message) &&
+ answer_message->getRRCount(Message::SECTION_ANSWER) > 0) {
+ dlog("Message found in cache, returning that");
+ // TODO: err, should cache set rcode as well?
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ // Perhaps we only have the one RRset?
+ // TODO: can we do this? should we check for specific types only?
+ RRsetPtr cached_rrset = cache_.lookup(question.getName(),
+ question.getType(),
+ question.getClass());
+ if (cached_rrset) {
+ dlog("Found single RRset in cache");
+ answer_message->addRRset(Message::SECTION_ANSWER,
+ cached_rrset);
+ answer_message->setRcode(Rcode::NOERROR());
+ crs->success(answer_message);
+ } else {
+ dlog("Message not found in cache, starting recursive query");
+ // It will delete itself when it is done
+ new RunningQuery(io, question, answer_message,
+ test_server_, buffer, crs, query_timeout_,
+ client_timeout_, lookup_timeout_, retries_,
+ nsas_, cache_);
+ }
+ }
+}
+
+void
+RecursiveQuery::forward(ConstMessagePtr query_message,
+ MessagePtr answer_message,
+ OutputBufferPtr buffer,
+ DNSServer* server)
+{
+ // XXX: eventually we will need to be able to determine whether
+ // the message should be sent via TCP or UDP, or sent initially via
+ // UDP and then fall back to TCP on failure, but for the moment
+ // we're only going to handle UDP.
+ IOService& io = dns_service_.getIOService();
+
+ isc::resolve::ResolverInterface::CallbackPtr crs(
+ new isc::resolve::ResolverCallbackServer(server));
+
+ // TODO: general 'prepareinitialanswer'
+ answer_message->setOpcode(isc::dns::Opcode::QUERY());
+ ConstQuestionPtr question = *query_message->beginQuestion();
+ answer_message->addQuestion(*question);
+
+ // implement the simplest forwarder, which will pass
+ // everything throught without interpretation, except
+ // QID, port number. The response will not be cached.
+ // It will delete itself when it is done
+ new ForwardQuery(io, query_message, answer_message,
+ upstream_, buffer, crs, query_timeout_,
+ client_timeout_, lookup_timeout_);
+}
+
+} // namespace asiolink
diff --git a/src/lib/resolve/recursive_query.h b/src/lib/resolve/recursive_query.h
new file mode 100644
index 0000000..e5a2177
--- /dev/null
+++ b/src/lib/resolve/recursive_query.h
@@ -0,0 +1,144 @@
+// 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 __RECURSIVE_QUERY_H
+#define __RECURSIVE_QUERY_H 1
+
+#include <asiolink/dns_service.h>
+#include <asiolink/dns_server.h>
+#include <dns/buffer.h>
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+namespace asiolink {
+/// \brief The \c RecursiveQuery class provides a layer of abstraction around
+/// the ASIO code that carries out an upstream query.
+///
+/// This design is very preliminary; currently it is only capable of
+/// handling simple forward requests to a single resolver.
+class RecursiveQuery {
+ ///
+ /// \name Constructors
+ ///
+ //@{
+public:
+ /// \brief Constructor
+ ///
+ /// This is currently the only way to construct \c RecursiveQuery
+ /// object. If the addresses of the forward nameservers is specified,
+ /// and every upstream query will be sent to one random address, and
+ /// the result sent back directly. If not, it will do full resolving.
+ ///
+ /// \param dns_service The DNS Service to perform the recursive
+ /// query on.
+ /// \param upstream Addresses and ports of the upstream servers
+ /// to forward queries to.
+ /// \param upstream_root Addresses and ports of the root servers
+ /// to use when resolving.
+ /// \param query_timeout Timeout value for queries we sent, in ms
+ /// \param client_timeout Timeout value for when we send back an
+ /// error, in ms
+ /// \param lookup_timeout Timeout value for when we give up, in ms
+ /// \param retries how many times we try again (0 means just send and
+ /// and return if it returs).
+ RecursiveQuery(DNSService& dns_service,
+ isc::nsas::NameserverAddressStore& nsas,
+ isc::cache::ResolverCache& cache,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream,
+ const std::vector<std::pair<std::string, uint16_t> >&
+ upstream_root,
+ int query_timeout = 2000,
+ int client_timeout = 4000,
+ int lookup_timeout = 30000,
+ unsigned retries = 3);
+ //@}
+
+ /// \brief Initiate resolving
+ ///
+ /// When sendQuery() is called, a (set of) message(s) is sent
+ /// asynchronously. If upstream servers are set, one is chosen
+ /// and the response (if any) from that server will be returned.
+ ///
+ /// If not upstream is set, a root server is chosen from the
+ /// root_servers, and the RunningQuery shall do a full resolve
+ /// (i.e. if the answer is a delegation, it will be followed, etc.)
+ /// until there is an answer or an error.
+ ///
+ /// When there is a response or an error and we give up, the given
+ /// CallbackPtr object shall be called (with either success() or
+ /// failure(). See ResolverInterface::Callback for more information.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param callback Callback object. See
+ /// \c ResolverInterface::Callback for more information
+ void resolve(const isc::dns::QuestionPtr& question,
+ const isc::resolve::ResolverInterface::CallbackPtr callback);
+
+
+ /// \brief Initiates resolving for the given question.
+ ///
+ /// This actually calls the previous sendQuery() with a default
+ /// callback object, which calls resume() on the given DNSServer
+ /// object.
+ ///
+ /// \param question The question being answered <qname/qclass/qtype>
+ /// \param answer_message An output Message into which the final response will be copied
+ /// \param buffer An output buffer into which the intermediate responses will be copied
+ /// \param server A pointer to the \c DNSServer object handling the client
+ void resolve(const isc::dns::Question& question,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server);
+
+ /// \brief Initiates forwarding for the given query.
+ ///
+ /// Others parameters are same with the parameters of
+ /// function resolve().
+ ///
+ /// \param query_message the full query got from client.
+ void forward(isc::dns::ConstMessagePtr query_message,
+ isc::dns::MessagePtr answer_message,
+ isc::dns::OutputBufferPtr buffer,
+ DNSServer* server);
+
+ /// \brief Set Test Server
+ ///
+ /// This method is *only* for unit testing the class. If set, it enables
+ /// recursive behaviour but, regardless of responses received, sends every
+ /// query to the test server.
+ ///
+ /// The test server is enabled by setting a non-zero port number.
+ ///
+ /// \param address IP address of the test server.
+ /// \param port Port number of the test server
+ void setTestServer(const std::string& address, uint16_t port);
+
+private:
+ DNSService& dns_service_;
+ isc::nsas::NameserverAddressStore& nsas_;
+ isc::cache::ResolverCache& cache_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_;
+ boost::shared_ptr<std::vector<std::pair<std::string, uint16_t> > >
+ upstream_root_;
+ std::pair<std::string, uint16_t> test_server_;
+ int query_timeout_;
+ int client_timeout_;
+ int lookup_timeout_;
+ unsigned retries_;
+};
+
+} // namespace asiolink
+#endif // __RECURSIVE_QUERY_H
diff --git a/src/lib/resolve/resolver_callback.h b/src/lib/resolve/resolver_callback.h
index f69d8a7..4244f19 100644
--- a/src/lib/resolve/resolver_callback.h
+++ b/src/lib/resolve/resolver_callback.h
@@ -15,9 +15,11 @@
#ifndef _ISC_RESOLVER_CALLBACK_H
#define _ISC_RESOLVER_CALLBACK_H 1
-#include <asiolink/asiolink.h>
+#include <asiolink/dns_server.h>
#include <dns/message.h>
+#include <resolve/resolver_interface.h>
+
namespace isc {
namespace resolve {
diff --git a/src/lib/resolve/resolver_interface.h b/src/lib/resolve/resolver_interface.h
index e08bb64..1d01e90 100644
--- a/src/lib/resolve/resolver_interface.h
+++ b/src/lib/resolve/resolver_interface.h
@@ -1,4 +1,4 @@
-// Copyright (C) 2010 CZ NIC
+// Copyright (C) 2010 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
diff --git a/src/lib/resolve/response_classifier.cc b/src/lib/resolve/response_classifier.cc
index 45e9cbc..02808e4 100644
--- a/src/lib/resolve/response_classifier.cc
+++ b/src/lib/resolve/response_classifier.cc
@@ -114,13 +114,17 @@ ResponseClassifier::Category ResponseClassifier::classify(
);
// If there is nothing in the answer section, it is a referral - unless
- // there is nothing in the authority section
+ // there is no NS in the authority section
if (answer.empty()) {
if (authority.empty()) {
return (EMPTY);
- } else {
- return (REFERRAL);
}
+ for (int i = 0; i < authority.size(); ++i) {
+ if (authority[i]->getType() == RRType::NS()) {
+ return (REFERRAL);
+ }
+ }
+ return (NXRRSET);
}
// Look at two cases - one RRset in the answer and multiple RRsets in
diff --git a/src/lib/resolve/response_classifier.h b/src/lib/resolve/response_classifier.h
index bee0628..3821560 100644
--- a/src/lib/resolve/response_classifier.h
+++ b/src/lib/resolve/response_classifier.h
@@ -53,6 +53,7 @@ public:
ANSWERCNAME, ///< Response was a CNAME chain ending in an answer
CNAME, ///< Response was a CNAME
NXDOMAIN, ///< Response was an NXDOMAIN
+ NXRRSET, ///< Response was name exists, but type does not
REFERRAL, ///< Response contains a referral
// Codes indicating that a message is invalid. Note that the error()
diff --git a/src/lib/resolve/tests/Makefile.am b/src/lib/resolve/tests/Makefile.am
index e669384..a403272 100644
--- a/src/lib/resolve/tests/Makefile.am
+++ b/src/lib/resolve/tests/Makefile.am
@@ -14,14 +14,22 @@ TESTS += run_unittests
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.h
+run_unittests_SOURCES += $(top_srcdir)/src/lib/dns/tests/unittest_util.cc
run_unittests_SOURCES += resolve_unittest.cc
run_unittests_SOURCES += resolver_callback_unittest.cc
run_unittests_SOURCES += response_classifier_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest.cc
+run_unittests_SOURCES += recursive_query_unittest_2.cc
run_unittests_LDADD = $(GTEST_LDADD)
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
+run_unittests_LDADD += $(top_builddir)/src/lib/nsas/libnsas.la
+run_unittests_LDADD += $(top_builddir)/src/lib/cache/libcache.la
+run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/resolve/libresolve.la
run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
+run_unittests_LDADD += $(top_builddir)/src/lib/log/liblog.la
endif
diff --git a/src/lib/resolve/tests/recursive_query_unittest.cc b/src/lib/resolve/tests/recursive_query_unittest.cc
new file mode 100644
index 0000000..94bf532
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest.cc
@@ -0,0 +1,873 @@
+// 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 <config.h>
+
+#include <sys/socket.h>
+#include <sys/time.h>
+
+#include <string.h>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+#include <gtest/gtest.h>
+
+#include <exceptions/exceptions.h>
+
+#include <dns/tests/unittest_util.h>
+#include <dns/rcode.h>
+
+#include <dns/buffer.h>
+#include <dns/message.h>
+
+#include <nsas/nameserver_address_store.h>
+#include <cache/resolver_cache.h>
+
+// IMPORTANT: We shouldn't directly use ASIO definitions in this test.
+// In particular, we must not include asio.hpp in this file.
+// The asiolink module is primarily intended to be a wrapper that hide the
+// details of the underlying implementations. We need to test the wrapper
+// level behaviors. In addition, some compilers reject to compile this file
+// if we include asio.hpp unless we specify a special compiler option.
+// If we need to test something at the level of underlying ASIO and need
+// their definition, that test should go to asiolink/internal/tests.
+#include <resolve/recursive_query.h>
+#include <asiolink/io_socket.h>
+#include <asiolink/io_service.h>
+#include <asiolink/io_message.h>
+#include <asiolink/io_error.h>
+#include <asiolink/dns_lookup.h>
+#include <asiolink/simple_callback.h>
+#include <resolve/resolve.h>
+
+using isc::UnitTestUtil;
+using namespace std;
+using namespace asiolink;
+using namespace isc::dns;
+
+namespace {
+const char* const TEST_SERVER_PORT = "53535";
+const char* const TEST_CLIENT_PORT = "53536";
+const char* const TEST_IPV6_ADDR = "::1";
+const char* const TEST_IPV4_ADDR = "127.0.0.1";
+// This data is intended to be valid as a DNS/TCP-like message: the first
+// two octets encode the length of the rest of the data. This is crucial
+// for the tests below.
+const uint8_t test_data[] = {0, 4, 1, 2, 3, 4};
+
+// This function returns an addrinfo structure for use by tests, using
+// different addresses and ports depending on whether we're testing
+// IPv4 or v6, TCP or UDP, and client or server operation.
+struct addrinfo*
+resolveAddress(const int family, const int protocol, const bool client) {
+ const char* const addr = (family == AF_INET6) ?
+ TEST_IPV6_ADDR : TEST_IPV4_ADDR;
+ const char* const port = client ? TEST_CLIENT_PORT : TEST_SERVER_PORT;
+
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = (protocol == IPPROTO_UDP) ? SOCK_DGRAM : SOCK_STREAM;
+ hints.ai_protocol = protocol;
+ hints.ai_flags = AI_NUMERICSERV;
+
+ struct addrinfo* res;
+ const int error = getaddrinfo(addr, port, &hints, &res);
+ if (error != 0) {
+ isc_throw(IOError, "getaddrinfo failed: " << gai_strerror(error));
+ }
+
+ return (res);
+}
+
+// This fixture is a framework for various types of network operations
+// using the ASIO interfaces. Each test case creates an IOService object,
+// opens a local "client" socket for testing, sends data via the local socket
+// to the service that would run in the IOService object.
+// A mock callback function (an ASIOCallBack object) is registered with the
+// IOService object, so the test code should be able to examine the data
+// received on the server side. It then checks the received data matches
+// expected parameters.
+// If initialization parameters of the IOService should be modified, the test
+// case can do it using the setDNSService() method.
+// Note: the set of tests in RecursiveQueryTest use actual network services and may
+// involve undesirable side effects such as blocking.
+class RecursiveQueryTest : public ::testing::Test {
+protected:
+ RecursiveQueryTest();
+ ~RecursiveQueryTest() {
+ if (res_ != NULL) {
+ freeaddrinfo(res_);
+ }
+ if (sock_ != -1) {
+ close(sock_);
+ }
+ delete dns_service_;
+ delete callback_;
+ delete io_service_;
+ }
+
+ // Send a test UDP packet to a mock server
+ void sendUDP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_UDP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ const int cc = sendto(sock_, test_data, sizeof(test_data), 0,
+ res_->ai_addr, res_->ai_addrlen);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected sendto result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Send a test TCP packet to a mock server
+ void sendTCP(const int family) {
+ res_ = resolveAddress(family, IPPROTO_TCP, false);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (connect(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to connect to the test server");
+ }
+ const int cc = send(sock_, test_data, sizeof(test_data), 0);
+ if (cc != sizeof(test_data)) {
+ isc_throw(IOError, "unexpected send result: " << cc);
+ }
+ io_service_->run();
+ }
+
+ // Receive a UDP packet from a mock server; used for testing
+ // recursive lookup. The caller must place a RecursiveQuery
+ // on the IO Service queue before running this routine.
+ void recvUDP(const int family, void* buffer, size_t& size) {
+ res_ = resolveAddress(family, IPPROTO_UDP, true);
+
+ sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "bind failed: " << strerror(errno));
+ }
+
+ // The IO service queue should have a RecursiveQuery object scheduled
+ // to run at this point. This call will cause it to begin an
+ // async send, then return.
+ io_service_->run_one();
+
+ // ... and this one will block until the send has completed
+ io_service_->run_one();
+
+ // Now we attempt to recv() whatever was sent.
+ // XXX: there's no guarantee the receiving socket can immediately get
+ // the packet. Normally we can perform blocking recv to wait for it,
+ // but in theory it's even possible that the packet is lost.
+ // In order to prevent the test from hanging in such a worst case
+ // we add an ad hoc timeout.
+ const struct timeval timeo = { 10, 0 };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo,
+ sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) {
+ // Workaround for Solaris: it doesn't accept SO_RCVTIMEO
+ // with the error of ENOPROTOOPT. Since this is a workaround
+ // for rare error cases anyway, we simply switch to the
+ // "don't wait" mode. If we still find an error in recv()
+ // can happen often we'll consider a more complete solution.
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ const int ret = recv(sock_, buffer, size, recv_options);
+ if (ret < 0) {
+ isc_throw(IOError, "recvfrom failed: " << strerror(errno));
+ }
+
+ // Pass the message size back via the size parameter
+ size = ret;
+ }
+
+
+ // Set up an IO Service queue using the specified address
+ void setDNSService(const char& address) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, address, callback_, NULL, NULL);
+ }
+
+ // Set up an IO Service queue using the "any" address, on IPv4 if
+ // 'use_ipv4' is true and on IPv6 if 'use_ipv6' is true.
+ void setDNSService(const bool use_ipv4, const bool use_ipv6) {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, *TEST_SERVER_PORT, use_ipv4, use_ipv6, callback_,
+ NULL, NULL);
+ }
+
+ // Set up empty DNS Service
+ // Set up an IO Service queue without any addresses
+ void setDNSService() {
+ delete dns_service_;
+ dns_service_ = NULL;
+ delete io_service_;
+ io_service_ = new IOService();
+ callback_ = new ASIOCallBack(this);
+ dns_service_ = new DNSService(*io_service_, callback_, NULL, NULL);
+ }
+
+ // Run a simple server test, on either IPv4 or IPv6, and over either
+ // UDP or TCP. Calls the sendUDP() or sendTCP() methods, which will
+ // start the IO Service queue. The UDPServer or TCPServer that was
+ // created by setIOService() will receive the test packet and issue a
+ // callback, which enables us to check that the data it received
+ // matches what we sent.
+ void doTest(const int family, const int protocol) {
+ if (protocol == IPPROTO_UDP) {
+ sendUDP(family);
+ } else {
+ sendTCP(family);
+ }
+
+ // There doesn't seem to be an effective test for the validity of
+ // 'native'.
+ // One thing we are sure is it must be different from our local socket.
+ EXPECT_NE(sock_, callback_native_);
+ EXPECT_EQ(protocol, callback_protocol_);
+ EXPECT_EQ(family == AF_INET6 ? TEST_IPV6_ADDR : TEST_IPV4_ADDR,
+ callback_address_);
+
+ const uint8_t* expected_data =
+ protocol == IPPROTO_UDP ? test_data : test_data + 2;
+ const size_t expected_datasize =
+ protocol == IPPROTO_UDP ? sizeof(test_data) :
+ sizeof(test_data) - 2;
+ EXPECT_PRED_FORMAT4(UnitTestUtil::matchWireData, &callback_data_[0],
+ callback_data_.size(),
+ expected_data, expected_datasize);
+ }
+
+protected:
+ // This is a nonfunctional mockup of a DNSServer object. Its purpose
+ // is to resume after a recursive query or other asynchronous call
+ // has completed.
+ class MockServer : public DNSServer {
+ public:
+ explicit MockServer(IOService& io_service,
+ SimpleCallback* checkin = NULL,
+ DNSLookup* lookup = NULL,
+ DNSAnswer* answer = NULL) :
+ io_(io_service),
+ done_(false),
+ message_(new Message(Message::PARSE)),
+ answer_message_(new Message(Message::RENDER)),
+ respbuf_(new OutputBuffer(0)),
+ checkin_(checkin), lookup_(lookup), answer_(answer)
+ {}
+
+ void operator()(asio::error_code ec = asio::error_code(),
+ size_t length = 0)
+ {}
+
+ void resume(const bool) {
+ // should never be called in our tests
+ }
+
+ DNSServer* clone() {
+ MockServer* s = new MockServer(*this);
+ return (s);
+ }
+
+ inline void asyncLookup() {
+ if (lookup_) {
+ (*lookup_)(*io_message_, message_, answer_message_,
+ respbuf_, this);
+ }
+ }
+
+ protected:
+ IOService& io_;
+ bool done_;
+
+ private:
+ // Currently unused; these will be used for testing
+ // asynchronous lookup calls via the asyncLookup() method
+ boost::shared_ptr<asiolink::IOMessage> io_message_;
+ isc::dns::MessagePtr message_;
+ isc::dns::MessagePtr answer_message_;
+ isc::dns::OutputBufferPtr respbuf_;
+
+ // Callback functions provided by the caller
+ const SimpleCallback* checkin_;
+ const DNSLookup* lookup_;
+ const DNSAnswer* answer_;
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ class MockServerStop : public MockServer {
+ public:
+ explicit MockServerStop(IOService& io_service, bool* done) :
+ MockServer(io_service),
+ done_(done)
+ {}
+
+ void resume(const bool done) {
+ *done_ = done;
+ io_.stop();
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop(*this));
+ }
+ private:
+ bool* done_;
+ };
+
+ class MockResolver : public isc::resolve::ResolverInterface {
+ void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+ };
+
+ // This version of mock server just stops the io_service when it is resumed
+ // the second time. (Used in the clientTimeout test, where resume
+ // is called initially with the error answer, and later when the
+ // lookup times out, it is called without an answer to send back)
+ class MockServerStop2 : public MockServer {
+ public:
+ explicit MockServerStop2(IOService& io_service,
+ bool* done1, bool* done2) :
+ MockServer(io_service),
+ done1_(done1),
+ done2_(done2),
+ stopped_once_(false)
+ {}
+
+ void resume(const bool done) {
+ if (stopped_once_) {
+ *done2_ = done;
+ io_.stop();
+ } else {
+ *done1_ = done;
+ stopped_once_ = true;
+ }
+ }
+
+ DNSServer* clone() {
+ return (new MockServerStop2(*this));
+ }
+ private:
+ bool* done1_;
+ bool* done2_;
+ bool stopped_once_;
+ };
+
+private:
+ class ASIOCallBack : public SimpleCallback {
+ public:
+ ASIOCallBack(RecursiveQueryTest* test_obj) : test_obj_(test_obj) {}
+ void operator()(const IOMessage& io_message) const {
+ test_obj_->callBack(io_message);
+ }
+ private:
+ RecursiveQueryTest* test_obj_;
+ };
+ void callBack(const IOMessage& io_message) {
+ callback_protocol_ = io_message.getSocket().getProtocol();
+ callback_native_ = io_message.getSocket().getNative();
+ callback_address_ =
+ io_message.getRemoteEndpoint().getAddress().toText();
+ callback_data_.assign(
+ static_cast<const uint8_t*>(io_message.getData()),
+ static_cast<const uint8_t*>(io_message.getData()) +
+ io_message.getDataSize());
+ io_service_->stop();
+ }
+protected:
+ // We use a pointer for io_service_, because for some tests we
+ // need to recreate a new one within one onstance of this class
+ IOService* io_service_;
+ DNSService* dns_service_;
+ isc::nsas::NameserverAddressStore* nsas_;
+ isc::cache::ResolverCache cache_;
+ ASIOCallBack* callback_;
+ int callback_protocol_;
+ int callback_native_;
+ string callback_address_;
+ vector<uint8_t> callback_data_;
+ int sock_;
+ struct addrinfo* res_;
+};
+
+RecursiveQueryTest::RecursiveQueryTest() :
+ dns_service_(NULL), callback_(NULL), callback_protocol_(0),
+ callback_native_(-1), sock_(-1), res_(NULL)
+{
+ io_service_ = new IOService();
+ setDNSService(true, true);
+ boost::shared_ptr<MockResolver>mock_resolver(new MockResolver());
+ nsas_ = new isc::nsas::NameserverAddressStore(mock_resolver);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSend) {
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSend) {
+ doTest(AF_INET6, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSend) {
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSend) {
+ doTest(AF_INET, IPPROTO_TCP);
+}
+
+TEST_F(RecursiveQueryTest, v6UDPSendSpecific) {
+ // Explicitly set a specific address to be bound to the socket.
+ // The subsequent test does not directly ensures the underlying socket
+ // is bound to the expected address, but the success of the tests should
+ // reasonably suggest it works as intended.
+ // Specifying an address also implicitly means the service runs in a
+ // single address-family mode. In tests using TCP we can confirm that
+ // by trying to make a connection and seeing a failure. In UDP, it'd be
+ // more complicated because we need to use a connected socket and catch
+ // an error on a subsequent read operation. We could do it, but for
+ // simplicity we only tests the easier cases for now.
+
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPSendSpecific) {
+ setDNSService(*TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4UDPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_UDP);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPSendSpecific) {
+ setDNSService(*TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV6_ADDR);
+ doTest(AF_INET6, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4AddServer) {
+ setDNSService();
+ dns_service_->addServer(*TEST_SERVER_PORT, TEST_IPV4_ADDR);
+ doTest(AF_INET, IPPROTO_TCP);
+
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, clearServers) {
+ setDNSService();
+ dns_service_->clearServers();
+
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v6TCPOnly) {
+ // Open only IPv6 TCP socket. A subsequent attempt of establishing an
+ // IPv4/TCP connection should fail. See above for why we only test this
+ // for TCP.
+ setDNSService(false, true);
+ EXPECT_THROW(sendTCP(AF_INET), IOError);
+}
+
+TEST_F(RecursiveQueryTest, v4TCPOnly) {
+ setDNSService(true, false);
+ EXPECT_THROW(sendTCP(AF_INET6), IOError);
+}
+
+vector<pair<string, uint16_t> >
+singleAddress(const string &address, uint16_t port) {
+ vector<pair<string, uint16_t> > result;
+ result.push_back(pair<string, uint16_t>(address, port));
+ return (result);
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV4) {
+ setDNSService(true, false);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port)));
+}
+
+TEST_F(RecursiveQueryTest, recursiveSetupV6) {
+ setDNSService(false, true);
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ EXPECT_NO_THROW(RecursiveQuery(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV6_ADDR, port),
+ singleAddress(TEST_IPV6_ADDR,port)));
+}
+
+// XXX:
+// This is very inadequate unit testing. It should be generalized into
+// a routine that can do this with variable address family, address, and
+// port, and with the various callbacks defined in such a way as to ensure
+// full code coverage including error cases.
+TEST_F(RecursiveQueryTest, forwarderSend) {
+ setDNSService(true, false);
+
+ // Note: We use the test prot plus one to ensure we aren't binding
+ // to the same port as the actual server
+ uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+
+ MockServer server(*io_service_);
+ RecursiveQuery rq(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port));
+
+ Question q(Name("example.com"), RRClass::IN(), RRType::TXT());
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(q, query_message);
+
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
+
+ char data[4096];
+ size_t size = sizeof(data);
+ ASSERT_NO_THROW(recvUDP(AF_INET, data, size));
+
+ Message m(Message::PARSE);
+ InputBuffer ibuf(data, size);
+
+ // Make sure we can parse the message that was sent
+ EXPECT_NO_THROW(m.parseHeader(ibuf));
+ EXPECT_NO_THROW(m.fromWire(ibuf));
+
+ // Check that the question sent matches the one we wanted
+ QuestionPtr q2 = *m.beginQuestion();
+ EXPECT_EQ(q.getName(), q2->getName());
+ EXPECT_EQ(q.getType(), q2->getType());
+ EXPECT_EQ(q.getClass(), q2->getClass());
+}
+
+int
+createTestSocket()
+{
+ struct addrinfo* res_ = resolveAddress(AF_INET, IPPROTO_UDP, true);
+ int sock_ = socket(res_->ai_family, res_->ai_socktype, res_->ai_protocol);
+ if (sock_ < 0) {
+ isc_throw(IOError, "failed to open test socket");
+ }
+ if (bind(sock_, res_->ai_addr, res_->ai_addrlen) < 0) {
+ isc_throw(IOError, "failed to bind test socket");
+ }
+ return sock_;
+}
+
+int
+setSocketTimeout(int sock_, size_t tv_sec, size_t tv_usec) {
+ const struct timeval timeo = { tv_sec, tv_usec };
+ int recv_options = 0;
+ if (setsockopt(sock_, SOL_SOCKET, SO_RCVTIMEO, &timeo, sizeof(timeo))) {
+ if (errno == ENOPROTOOPT) { // see RecursiveQueryTest::recvUDP()
+ recv_options = MSG_DONTWAIT;
+ } else {
+ isc_throw(IOError, "set RCVTIMEO failed: " << strerror(errno));
+ }
+ }
+ return recv_options;
+}
+
+// try to read from the socket max time
+// *num is incremented for every succesfull read
+// returns true if it can read max times, false otherwise
+bool tryRead(int sock_, int recv_options, size_t max, int* num) {
+ size_t i = 0;
+ do {
+ char inbuff[512];
+ if (recv(sock_, inbuff, sizeof(inbuff), recv_options) < 0) {
+ return false;
+ } else {
+ ++i;
+ ++*num;
+ }
+ } while (i < max);
+ return true;
+}
+
+
+// Test it tries the correct amount of times before giving up
+TEST_F(RecursiveQueryTest, forwardQueryTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 10, 4000, 3000, 2);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
+ // Run the test
+ io_service_->run();
+
+ // Read up to 3 packets. Use some ad hoc timeout to prevent an infinite
+ // block (see also recvUDP()).
+ int recv_options = setSocketTimeout(sock_, 10, 0);
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 3, &num);
+
+ // The query should 'succeed' with an error response
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_TRUE(read_success);
+}
+
+// If we set client timeout to lower than querytimeout, we should
+// get a failure answer, but still see retries
+// (no actual answer is given here yet)
+TEST_F(RecursiveQueryTest, forwardClientTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done1(true);
+ MockServerStop server(*io_service_, &done1);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set it up to retry twice before client timeout fires
+ // Since the lookup timer has not fired, it should retry
+ // four times
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 480, 4000, 4);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ // we know it'll fail, so make it a shorter timeout
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 4 times
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 4, &num);
+
+ // The query should fail
+ EXPECT_TRUE(done1);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// If we set lookup timeout to lower than querytimeout*retries, we should
+// fail before the full amount of retries
+TEST_F(RecursiveQueryTest, forwardLookupTimeout) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 200, 4000, 480, 5);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+
+ Message query_message(Message::RENDER);
+ isc::resolve::initResponseMessage(question, query_message);
+ query.forward(ConstMessagePtr(&query_message), answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(3, num);
+ EXPECT_FALSE(read_success);
+}
+
+// Set everything very low and see if this doesn't cause weird
+// behaviour
+TEST_F(RecursiveQueryTest, lowtimeouts) {
+ // Prepare the service (we do not use the common setup, we do not answer
+ setDNSService();
+
+ // Prepare the socket
+ sock_ = createTestSocket();
+
+ // Prepare the server
+ bool done(true);
+ MockServerStop server(*io_service_, &done);
+
+ MessagePtr answer(new Message(Message::RENDER));
+
+ // Do the answer
+ const uint16_t port = boost::lexical_cast<uint16_t>(TEST_CLIENT_PORT);
+ // Set up the test so that it will retry 5 times, but the lookup
+ // timeout will fire after only 3 normal timeouts
+ RecursiveQuery query(*dns_service_,
+ *nsas_, cache_,
+ singleAddress(TEST_IPV4_ADDR, port),
+ singleAddress(TEST_IPV4_ADDR, port),
+ 1, 1, 1, 1);
+ Question question(Name("example.net"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ query.resolve(question, answer, buffer, &server);
+
+ // Run the test
+ io_service_->run();
+
+ int recv_options = setSocketTimeout(sock_, 1, 0);
+
+ // Try to read 5 times, should stop after 3 reads
+ int num = 0;
+ bool read_success = tryRead(sock_, recv_options, 5, &num);
+
+ // The query should fail and respond with an error
+ EXPECT_TRUE(done);
+ EXPECT_EQ(1, num);
+ EXPECT_FALSE(read_success);
+}
+
+// as mentioned above, we need a more better framework for this,
+// in addition to that, this sends out queries into the world
+// (which we should catch somehow and fake replies for)
+// for the skeleton code, it shouldn't be too much of a problem
+// Ok so even we don't all have access to the DNS world right now,
+// so disabling these tests too.
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendOk) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("www.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NOERROR(), answer->getRcode());
+ ASSERT_EQ(1, answer->getRRCount(Message::SECTION_ANSWER));
+ RRsetPtr a = *answer->beginSection(Message::SECTION_ANSWER);
+ EXPECT_EQ(q.getName(), a->getName());
+ EXPECT_EQ(q.getType(), a->getType());
+ EXPECT_EQ(q.getClass(), a->getClass());
+ EXPECT_EQ(1, a->getRdataCount());
+}
+
+// see comments at previous test
+TEST_F(RecursiveQueryTest, DISABLED_recursiveSendNXDOMAIN) {
+ setDNSService(true, false);
+ bool done;
+
+ MockServerStop server(*io_service_, &done);
+ vector<pair<string, uint16_t> > empty_vector;
+ RecursiveQuery rq(*dns_service_, *nsas_, cache_, empty_vector,
+ empty_vector, 10000, 0);
+
+ Question q(Name("wwwdoesnotexist.isc.org"), RRClass::IN(), RRType::A());
+ OutputBufferPtr buffer(new OutputBuffer(0));
+ MessagePtr answer(new Message(Message::RENDER));
+ rq.resolve(q, answer, buffer, &server);
+ io_service_->run();
+
+ // Check that the answer we got matches the one we wanted
+ EXPECT_EQ(Rcode::NXDOMAIN(), answer->getRcode());
+ EXPECT_EQ(0, answer->getRRCount(Message::SECTION_ANSWER));
+}
+
+// TODO: add tests that check whether the cache is updated on succesfull
+// responses, and not updated on failures.
+
+}
diff --git a/src/lib/resolve/tests/recursive_query_unittest_2.cc b/src/lib/resolve/tests/recursive_query_unittest_2.cc
new file mode 100644
index 0000000..643c5a3
--- /dev/null
+++ b/src/lib/resolve/tests/recursive_query_unittest_2.cc
@@ -0,0 +1,677 @@
+// 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 <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include <iostream>
+#include <string>
+
+#include <gtest/gtest.h>
+#include <boost/bind.hpp>
+
+#include <asio.hpp>
+
+#include <dns/buffer.h>
+#include <dns/question.h>
+#include <dns/message.h>
+#include <dns/messagerenderer.h>
+#include <dns/opcode.h>
+#include <dns/name.h>
+#include <dns/rcode.h>
+#include <dns/rrtype.h>
+#include <dns/rrset.h>
+#include <dns/rrttl.h>
+#include <dns/rdata.h>
+
+#include <asiolink/asiolink_utilities.h>
+#include <asiolink/dns_service.h>
+#include <asiolink/io_address.h>
+#include <asiolink/io_endpoint.h>
+#include <asiolink/io_fetch.h>
+#include <asiolink/io_service.h>
+#include <resolve/recursive_query.h>
+#include <resolve/resolver_interface.h>
+
+using namespace asio;
+using namespace asio::ip;
+using namespace isc::dns;
+using namespace isc::dns::rdata;
+using namespace isc::resolve;
+using namespace std;
+
+/// RecursiveQuery Test - 2
+///
+/// The second part of the RecursiveQuery unit tests, this attempts to get the
+/// RecursiveQuery object to follow a set of referrals for "www.example.org" to
+/// and to invoke TCP fallback on one of the queries. In particular, we expect
+/// that the test will do the following in an attempt to resolve
+/// www.example.org:
+///
+/// - Send question over UDP to "root" - get referral to "org".
+/// - Send question over UDP to "org" - get referral to "example.org" with TC bit set.
+/// - Send question over TCP to "org" - get referral to "example.org".
+/// - Send question over UDP to "example.org" - get response for www.example.org.
+///
+/// (The order of queries is set in this way in order to also test that after a
+/// failover to TCP, queries revert to UDP).
+///
+/// By using the "test_server_" element of RecursiveQuery, all queries are
+/// directed to one or other of the "servers" in the RecursiveQueryTest2 class,
+/// regardless of the glue returned in referrals.
+
+namespace asiolink {
+
+const std::string TEST_ADDRESS = "127.0.0.1"; ///< Servers are on this address
+const uint16_t TEST_PORT = 5301; ///< ... and this port
+const size_t BUFFER_SIZE = 1024; ///< For all buffers
+const char* WWW_EXAMPLE_ORG = "192.0.2.254"; ///< Address of www.example.org
+
+// As the test is fairly long and complex, debugging "print" statements have
+// been left in although they are disabled. Set the following to "true" to
+// enable them.
+const bool DEBUG_PRINT = false;
+
+class MockResolver : public isc::resolve::ResolverInterface {
+public:
+ virtual void resolve(const QuestionPtr& question,
+ const ResolverInterface::CallbackPtr& callback) {
+ }
+
+ virtual ~MockResolver() {}
+};
+
+
+
+/// \brief Test fixture for the RecursiveQuery Test
+class RecursiveQueryTest2 : public virtual ::testing::Test
+{
+public:
+
+ /// \brief Status of query
+ ///
+ /// Set before the query and then by each "server" when responding.
+ enum QueryStatus {
+ NONE = 0, ///< Default
+ UDP_ROOT = 1, ///< Query root server over UDP
+ UDP_ORG = 2, ///< Query ORG server over UDP
+ TCP_ORG = 3, ///< Query ORG server over TCP
+ UDP_EXAMPLE_ORG_BAD = 4, ///< Query EXAMPLE.ORG server over UDP
+ ///< (return malformed packet)
+ UDP_EXAMPLE_ORG = 5, ///< Query EXAMPLE.ORG server over UDP
+ COMPLETE = 6 ///< Query is complete
+ };
+
+ // Common stuff
+ bool debug_; ///< Set true for debug print
+ IOService service_; ///< Service to run everything
+ DNSService dns_service_; ///< Resolver is part of "server"
+ QuestionPtr question_; ///< What to ask
+ QueryStatus last_; ///< What was the last state
+ QueryStatus expected_; ///< Expected next state
+ OutputBufferPtr question_buffer_; ///< Question we expect to receive
+ boost::shared_ptr<MockResolver> resolver_; ///< Mock resolver
+ isc::nsas::NameserverAddressStore* nsas_; ///< Nameserver address store
+ isc::cache::ResolverCache cache_; ///< Resolver cache
+
+ // Data for TCP Server
+ size_t tcp_cumulative_; ///< Cumulative TCP data received
+ tcp::endpoint tcp_endpoint_; ///< Endpoint for TCP receives
+ size_t tcp_length_; ///< Expected length value
+ uint8_t tcp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for TCP I/O
+ OutputBufferPtr tcp_send_buffer_; ///< Send buffer for TCP I/O
+ tcp::socket tcp_socket_; ///< Socket used by TCP server
+
+ /// Data for UDP
+ udp::endpoint udp_remote_; ///< Endpoint for UDP receives
+ size_t udp_length_; ///< Expected length value
+ uint8_t udp_receive_buffer_[BUFFER_SIZE]; ///< Receive buffer for UDP I/O
+ OutputBufferPtr udp_send_buffer_; ///< Send buffer for UDP I/O
+ udp::socket udp_socket_; ///< Socket used by UDP server
+
+ /// \brief Constructor
+ RecursiveQueryTest2() :
+ debug_(DEBUG_PRINT),
+ service_(),
+ dns_service_(service_, NULL, NULL, NULL),
+ question_(new Question(Name("www.example.org"), RRClass::IN(), RRType::A())),
+ last_(NONE),
+ expected_(NONE),
+ question_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ resolver_(new MockResolver()),
+ nsas_(new isc::nsas::NameserverAddressStore(resolver_)),
+ tcp_cumulative_(0),
+ tcp_endpoint_(asio::ip::address::from_string(TEST_ADDRESS), TEST_PORT),
+ tcp_length_(0),
+ tcp_receive_buffer_(),
+ tcp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ tcp_socket_(service_.get_io_service()),
+ udp_remote_(),
+ udp_length_(0),
+ udp_receive_buffer_(),
+ udp_send_buffer_(new OutputBuffer(BUFFER_SIZE)),
+ udp_socket_(service_.get_io_service(), udp::v4())
+ {
+ }
+
+ /// \brief Set Common Message Bits
+ ///
+ /// Sets up the common bits of a response message returned by the handlers.
+ ///
+ /// \param msg Message buffer in RENDER mode.
+ /// \param qid QID to set the message to
+ void setCommonMessage(isc::dns::Message& msg, uint16_t qid = 0) {
+ msg.setQid(qid);
+ msg.setHeaderFlag(Message::HEADERFLAG_QR);
+ msg.setOpcode(Opcode::QUERY());
+ msg.setHeaderFlag(Message::HEADERFLAG_AA);
+ msg.setRcode(Rcode::NOERROR());
+ msg.addQuestion(*question_);
+ }
+
+ /// \brief Set Referral to "org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious .org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralOrg(): creating referral to .org nameservers" << endl;
+ }
+
+ // Do a referral to org. We'll define all NS records as "in-zone"
+ // nameservers (and supply glue) to avoid the possibility of the
+ // resolver starting another recursive query to resolve the address of
+ // a nameserver.
+ RRsetPtr org_ns(new RRset(Name("org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.org."));
+ org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, org_ns);
+
+ RRsetPtr org_ns1(new RRset(Name("ns1.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.1"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns1);
+
+ RRsetPtr org_ns2(new RRset(Name("ns2.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.2"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, org_ns2);
+ }
+
+ /// \brief Set Referral to "example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode to
+ /// indicate a referral to fictitious example.org nameservers.
+ ///
+ /// \param msg Message to update with referral information.
+ void setReferralExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setReferralExampleOrg(): creating referral to example.org nameservers" << endl;
+ }
+
+ // Do a referral to example.org. As before, we'll define all NS
+ // records as "in-zone" nameservers (and supply glue) to avoid the
+ // possibility of the resolver starting another recursive query to look
+ // up the address of the nameserver.
+ RRsetPtr example_org_ns(new RRset(Name("example.org."), RRClass::IN(), RRType::NS(), RRTTL(300)));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns1.example.org."));
+ example_org_ns->addRdata(createRdata(RRType::NS(), RRClass::IN(), "ns2.example.org."));
+ msg.addRRset(Message::SECTION_AUTHORITY, example_org_ns);
+
+ RRsetPtr example_org_ns1(new RRset(Name("ns1.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns1->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.11"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns1);
+
+ RRsetPtr example_org_ns2(new RRset(Name("ns2.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ example_org_ns2->addRdata(createRdata(RRType::A(), RRClass::IN(), "192.0.2.21"));
+ msg.addRRset(Message::SECTION_ADDITIONAL, example_org_ns2);
+ }
+
+ /// \brief Set Answer to "www.example.org"
+ ///
+ /// Sets up the passed-in message (expected to be in "RENDER" mode) to
+ /// indicate an authoritative answer to www.example.org.
+ ///
+ /// \param msg Message to update with referral information.
+ void setAnswerWwwExampleOrg(isc::dns::Message& msg) {
+ if (debug_) {
+ cout << "setAnswerWwwExampleOrg(): creating answer for www.example.org" << endl;
+ }
+
+ // Give a response for www.example.org.
+ RRsetPtr www_example_org_a(new RRset(Name("www.example.org."), RRClass::IN(), RRType::A(), RRTTL(300)));
+ www_example_org_a->addRdata(createRdata(RRType::A(), RRClass::IN(), WWW_EXAMPLE_ORG));
+ msg.addRRset(Message::SECTION_ANSWER, www_example_org_a);
+
+ // ... and add the Authority and Additional sections. (These are the
+ // same as in the referral to example.org from the .org nameserver.)
+ setReferralExampleOrg(msg);
+ }
+
+ /// \brief UDP Receive Handler
+ ///
+ /// This is invoked when a message is received over UDP from the
+ /// RecursiveQuery object under test. It formats an answer and sends it
+ /// asynchronously, with the UdpSendHandler method being specified as the
+ /// completion handler.
+ ///
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void udpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length << ", last state = " << last_ <<
+ ", expected state = " << expected_ << endl;
+ }
+
+ // Expected state should be one greater than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // The QID in the incoming data is random so set it to 0 for the
+ // data comparison check. (It is set to 0 in the buffer containing
+ // the expected data.)
+ // And check that question we received is what was expected.
+ uint16_t qid = checkReceivedPacket(udp_receive_buffer_, length);
+
+ // The message returned depends on what state we are in. Set up
+ // common stuff first: bits not mentioned are set to 0.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, qid);
+
+ // In the case of UDP_EXAMPLE_ORG_BAD, we shall mangle the
+ // response
+ bool mangle_response = false;
+
+ // Set up state-dependent bits:
+ switch (expected_) {
+ case UDP_ROOT:
+ // Return a referral to org. We then expect to query the "org"
+ // nameservers over UDP next.
+ setReferralOrg(msg);
+ expected_ = UDP_ORG;
+ break;
+
+ case UDP_ORG:
+ // Return a referral to example.org. We explicitly set the TC bit to
+ // force a repeat query to the .org nameservers over TCP.
+ setReferralExampleOrg(msg);
+ if (debug_) {
+ cout << "udpReceiveHandler(): setting TC bit" << endl;
+ }
+ msg.setHeaderFlag(Message::HEADERFLAG_TC);
+ expected_ = TCP_ORG;
+ break;
+
+ case UDP_EXAMPLE_ORG_BAD:
+ // Return the answer to the question.
+ setAnswerWwwExampleOrg(msg);
+ // Mangle the response to enfore another query
+ mangle_response = true;
+ expected_ = UDP_EXAMPLE_ORG;
+ break;
+
+ case UDP_EXAMPLE_ORG:
+ // Return the answer to the question.
+ setAnswerWwwExampleOrg(msg);
+ expected_ = COMPLETE;
+ break;
+
+ default:
+ FAIL() << "UdpReceiveHandler called with unknown state";
+ }
+
+ // Convert to wire format
+ udp_send_buffer_->clear();
+ MessageRenderer renderer(*udp_send_buffer_);
+ msg.toWire(renderer);
+
+ if (mangle_response) {
+ // mangle the packet a bit
+ // set additional to one more
+ udp_send_buffer_->writeUint8At(3, 11);
+ }
+
+ // Return a message back to the IOFetch object (after setting the
+ // expected length of data for the check in the send handler).
+ udp_length_ = udp_send_buffer_->getLength();
+ udp_socket_.async_send_to(asio::buffer(udp_send_buffer_->getData(),
+ udp_send_buffer_->getLength()),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpSendHandler,
+ this, _1, _2));
+ }
+
+ /// \brief UDP Send Handler
+ ///
+ /// Called when a send operation of the UDP server (i.e. a response
+ /// being sent to the RecursiveQuery) has completed, this re-issues
+ /// a read call.
+ ///
+ /// \param ec Completion error code of the send.
+ /// \param length Actual number of bytes sent.
+ void udpSendHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "udpSendHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Check send was OK
+ EXPECT_EQ(0, ec.value());
+ EXPECT_EQ(udp_length_, length);
+
+ // Reissue the receive call to await the next message.
+ udp_socket_.async_receive_from(
+ asio::buffer(udp_receive_buffer_, sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Accepting TCP Data
+ ///
+ /// Called when the remote system connects to the "TCP server". It issues
+ /// an asynchronous read on the socket to read data.
+ ///
+ /// \param socket Socket on which data will be received
+ /// \param ec Boost error code, value should be zero.
+ void tcpAcceptHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpAcceptHandler(): error = " << ec.value() <<
+ ", length = " << length << endl;
+ }
+
+ // Expect that the accept completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Initiate a read on the socket, indicating that nothing has yet been
+ // received.
+ tcp_cumulative_ = 0;
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_, sizeof(tcp_receive_buffer_)),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ }
+
+ /// \brief Completion Handler for Receiving TCP Data
+ ///
+ /// Reads data from the RecursiveQuery object and loops, reissuing reads,
+ /// until all the message has been read. It then returns an appropriate
+ /// response.
+ ///
+ /// \param socket Socket to use to send the answer
+ /// \param ec ASIO error code, completion code of asynchronous I/O issued
+ /// by the "server" to receive data.
+ /// \param length Amount of data received.
+ void tcpReceiveHandler(error_code ec = error_code(), size_t length = 0) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", cumulative = " << tcp_cumulative_ << endl;
+ }
+
+ // Expect that the receive completed without a problem.
+ EXPECT_EQ(0, ec.value());
+
+ // Have we received all the data? We know this by checking if the two-
+ // byte length count in the message is equal to the data received.
+ tcp_cumulative_ += length;
+ bool complete = false;
+ if (tcp_cumulative_ > 2) {
+ uint16_t dns_length = readUint16(tcp_receive_buffer_);
+ complete = ((dns_length + 2) == tcp_cumulative_);
+ }
+
+ if (!complete) {
+ if (debug_) {
+ cout << "tcpReceiveHandler(): read not complete, " <<
+ "issuing another read" << endl;
+ }
+
+ // Not complete yet, issue another read.
+ tcp_socket_.async_receive(
+ asio::buffer(tcp_receive_buffer_ + tcp_cumulative_,
+ sizeof(tcp_receive_buffer_) - tcp_cumulative_),
+ boost::bind(&RecursiveQueryTest2::tcpReceiveHandler, this, _1, _2));
+ return;
+ }
+
+ // Have received a TCP message. Expected state should be one greater
+ // than the last state.
+ EXPECT_EQ(static_cast<int>(expected_), static_cast<int>(last_) + 1);
+ last_ = expected_;
+
+ // Check that question we received is what was expected. Note that we
+ // have to ignore the two-byte header in order to parse the message.
+ qid_t qid = checkReceivedPacket(tcp_receive_buffer_ + 2, length - 2);
+
+ // Return a message back. This is a referral to example.org, which
+ // should result in another query over UDP. Note the setting of the
+ // QID in the returned message with what was in the received message.
+ Message msg(Message::RENDER);
+ setCommonMessage(msg, qid);
+ setReferralExampleOrg(msg);
+
+ // Convert to wire format
+ // Use a temporary buffer for the dns wire data (we copy it
+ // to the 'real' buffer below)
+ OutputBuffer msg_buf(BUFFER_SIZE);
+ MessageRenderer renderer(msg_buf);
+ msg.toWire(renderer);
+
+ // Expected next state (when checked) is the UDP query to example.org.
+ // Also, take this opportunity to clear the accumulated read count in
+ // readiness for the next read. (If any - at present, there is only
+ // one read in the test, although extensions to this test suite could
+ // change that.)
+ expected_ = UDP_EXAMPLE_ORG_BAD;
+ tcp_cumulative_ = 0;
+
+ // Unless we go through a callback loop we cannot simply use
+ // async_send() multiple times, so we cannot send the size first
+ // followed by the actual data. We copy them to a new buffer
+ // first
+ tcp_send_buffer_->clear();
+ tcp_send_buffer_->writeUint16(msg_buf.getLength());
+ tcp_send_buffer_->writeData(msg_buf.getData(), msg_buf.getLength());
+ tcp_socket_.async_send(asio::buffer(tcp_send_buffer_->getData(),
+ tcp_send_buffer_->getLength()),
+ boost::bind(&RecursiveQueryTest2::tcpSendHandler, this,
+ tcp_send_buffer_->getLength(), _1, _2));
+ }
+
+ /// \brief Completion Handler for Sending TCP data
+ ///
+ /// Called when the asynchronous send of data back to the RecursiveQuery
+ /// by the TCP "server" in this class has completed. (This send has to
+ /// be asynchronous because control needs to return to the caller in order
+ /// for the IOService "run()" method to be called to run the handlers.)
+ ///
+ /// \param expected_length Number of bytes that were expected to have been sent.
+ /// \param ec Boost error code, value should be zero.
+ /// \param length Number of bytes sent.
+ void tcpSendHandler(size_t expected_length = 0, error_code ec = error_code(),
+ size_t length = 0)
+ {
+ if (debug_) {
+ cout << "tcpSendHandler(): error = " << ec.value() <<
+ ", length = " << length <<
+ ", (expected length = " << expected_length << ")" << endl;
+ }
+ EXPECT_EQ(0, ec.value()); // Expect no error
+ EXPECT_EQ(expected_length, length); // And that amount sent is as expected
+ }
+
+ /// \brief Check Received Packet
+ ///
+ /// Checks the packet received from the RecursiveQuery object to ensure
+ /// that the question is what is expected.
+ ///
+ /// \param data Start of data. This is the start of the received buffer in
+ /// the case of UDP data, and an offset into the buffer past the
+ /// count field for TCP data.
+ /// \param length Length of data.
+ /// \return The QID of the message
+ qid_t checkReceivedPacket(uint8_t* data, size_t length) {
+
+ // Decode the received buffer.
+ InputBuffer buffer(data, length);
+ Message message(Message::PARSE);
+ message.fromWire(buffer);
+
+ // Check the packet.
+ EXPECT_FALSE(message.getHeaderFlag(Message::HEADERFLAG_QR));
+
+ Question question = **(message.beginQuestion());
+ EXPECT_TRUE(question == *question_);
+
+ return message.getQid();
+ }
+};
+
+/// \brief Resolver Callback Object
+///
+/// Holds the success and failure callback methods for the resolver
+class ResolverCallback : public isc::resolve::ResolverInterface::Callback {
+public:
+ /// \brief Constructor
+ ResolverCallback(IOService& service) :
+ service_(service), run_(false), status_(false), debug_(DEBUG_PRINT)
+ {}
+
+ /// \brief Destructor
+ virtual ~ResolverCallback()
+ {}
+
+ /// \brief Resolver Callback Success
+ ///
+ /// Called if the resolver detects that the call has succeeded.
+ ///
+ /// \param response Answer to the question.
+ virtual void success(const isc::dns::MessagePtr response) {
+ if (debug_) {
+ cout << "ResolverCallback::success(): answer received" << endl;
+ cout << response->toText() << endl;
+ }
+
+ // There should be one RR each in the question and answer sections, and
+ // two RRs in each of the the authority and additional sections.
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_QUESTION));
+ EXPECT_EQ(1, response->getRRCount(Message::SECTION_ANSWER));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_AUTHORITY));
+ EXPECT_EQ(2, response->getRRCount(Message::SECTION_ADDITIONAL));
+
+ // Check the answer - that the RRset is there...
+ EXPECT_TRUE(response->hasRRset(Message::SECTION_ANSWER,
+ RRsetPtr(new RRset(Name("www.example.org."),
+ RRClass::IN(),
+ RRType::A(),
+ RRTTL(300)))));
+ const RRsetIterator rrset_i = response->beginSection(Message::SECTION_ANSWER);
+
+ // ... get iterator into the Rdata of this RRset and point to first
+ // element...
+ const RdataIteratorPtr rdata_i = (*rrset_i)->getRdataIterator();
+ rdata_i->first();
+
+ // ... and check it is what we expect.
+ EXPECT_EQ(string(WWW_EXAMPLE_ORG), rdata_i->getCurrent().toText());
+
+ // Flag completion
+ run_ = true;
+ status_ = true;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Resolver Failure Completion
+ ///
+ /// Called if the resolver detects that the resolution has failed.
+ virtual void failure() {
+ if (debug_) {
+ cout << "ResolverCallback::success(): resolution failure" << endl;
+ }
+ FAIL() << "Resolver reported completion failure";
+
+ // Flag completion
+ run_ = true;
+ status_ = false;
+
+ service_.stop(); // Cause run() to exit.
+ }
+
+ /// \brief Return status of "run" flag
+ bool getRun() const {
+ return (run_);
+ }
+
+ /// \brief Return "status" flag
+ bool getStatus() const {
+ return (status_);
+ }
+
+private:
+ IOService& service_; ///< Service handling the run queue
+ bool run_; ///< Set true when completion handler run
+ bool status_; ///< Set true for success, false on error
+ bool debug_; ///< Debug flag
+};
+
+// Sets up the UDP and TCP "servers", then tries a resolution.
+
+TEST_F(RecursiveQueryTest2, Resolve) {
+ // Set up the UDP server and issue the first read. The endpoint from which
+ // the query is sent is put in udp_endpoint_ when the read completes, which
+ // is referenced in the callback as the place to which the response is sent.
+ udp_socket_.set_option(socket_base::reuse_address(true));
+ udp_socket_.bind(udp::endpoint(address::from_string(TEST_ADDRESS), TEST_PORT));
+ udp_socket_.async_receive_from(asio::buffer(udp_receive_buffer_,
+ sizeof(udp_receive_buffer_)),
+ udp_remote_,
+ boost::bind(&RecursiveQueryTest2::udpReceiveHandler,
+ this, _1, _2));
+
+ // Set up the TCP server and issue the accept. Acceptance will cause the
+ // read to be issued.
+ tcp::acceptor acceptor(service_.get_io_service(),
+ tcp::endpoint(tcp::v4(), TEST_PORT));
+ acceptor.async_accept(tcp_socket_,
+ boost::bind(&RecursiveQueryTest2::tcpAcceptHandler,
+ this, _1, 0));
+
+ // Set up the RecursiveQuery object.
+ std::vector<std::pair<std::string, uint16_t> > upstream; // Empty
+ std::vector<std::pair<std::string, uint16_t> > upstream_root; // Empty
+ RecursiveQuery query(dns_service_, *nsas_, cache_,
+ upstream, upstream_root);
+ query.setTestServer(TEST_ADDRESS, TEST_PORT);
+
+ // Set up callback to receive notification that the query has completed.
+ isc::resolve::ResolverInterface::CallbackPtr
+ resolver_callback(new ResolverCallback(service_));
+
+ // Kick off the resolution process. We expect the first question to go to
+ // "root".
+ expected_ = UDP_ROOT;
+ query.resolve(question_, resolver_callback);
+ service_.run();
+
+ // Check what ran. (We have to cast the callback to ResolverCallback as we
+ // lost the information on the derived class when we used a
+ // ResolverInterface::CallbackPtr to store a pointer to it.)
+ ResolverCallback* rc = static_cast<ResolverCallback*>(resolver_callback.get());
+ EXPECT_TRUE(rc->getRun());
+ EXPECT_TRUE(rc->getStatus());
+}
+
+} // namespace asiolink
diff --git a/src/lib/resolve/tests/resolver_callback_unittest.cc b/src/lib/resolve/tests/resolver_callback_unittest.cc
index 6370e22..666b853 100644
--- a/src/lib/resolve/tests/resolver_callback_unittest.cc
+++ b/src/lib/resolve/tests/resolver_callback_unittest.cc
@@ -13,8 +13,8 @@
// PERFORMANCE OF THIS SOFTWARE.
#include <gtest/gtest.h>
+#include <asiolink/dns_server.h>
#include <resolve/resolver_callback.h>
-#include <asiolink/asiolink.h>
using namespace isc::resolve;
diff --git a/src/lib/resolve/tests/response_classifier_unittest.cc b/src/lib/resolve/tests/response_classifier_unittest.cc
index b37ded7..23c8666 100644
--- a/src/lib/resolve/tests/response_classifier_unittest.cc
+++ b/src/lib/resolve/tests/response_classifier_unittest.cc
@@ -80,6 +80,8 @@ public:
RRType::CNAME(), RRTTL(300))),
rrs_in_ns_(new RRset(Name("example.com"), RRClass::IN(),
RRType::NS(), RRTTL(300))),
+ rrs_in_soa_(new RRset(Name("example.com"), RRClass::IN(),
+ RRType::SOA(), RRTTL(300))),
rrs_in_txt_www(new RRset(Name("www.example.com"), RRClass::IN(),
RRType::TXT(), RRTTL(300))),
cname_target("."),
@@ -115,6 +117,9 @@ public:
// Set up an imaginary NS RRset for an authority section
rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.isc.org"))));
rrs_in_ns_->addRdata(ConstRdataPtr(new NS(Name("ns0.example.org"))));
+
+ // And an imaginary SOA
+ rrs_in_soa_->addRdata(ConstRdataPtr(new SOA(Name("ns0.example.org"), Name("root.example.org"), 1, 2, 3, 4, 5)));
// Set up the records for the www host
rrs_in_a_www->addRdata(ConstRdataPtr(new A("1.2.3.4")));
@@ -146,6 +151,7 @@ public:
RRsetPtr rrs_in_cname_www1; // www1.example.com IN CNAME
RRsetPtr rrs_in_cname_www2; // www2.example.com IN CNAME
RRsetPtr rrs_in_ns_; // example.com IN NS
+ RRsetPtr rrs_in_soa_; // example.com IN SOA
RRsetPtr rrs_in_txt_www; // www.example.com IN TXT
Name cname_target; // Used in response classifier to
// store the target of a possible
@@ -349,6 +355,17 @@ TEST_F(ResponseClassifierTest, EmptyAnswerReferral) {
}
+// Test if we get a NOERROR answer that contains neither an actual
+// answer nor a delegation
+TEST_F(ResponseClassifierTest, NoErrorNoData) {
+
+ msg_a.addRRset(Message::SECTION_AUTHORITY, rrs_in_soa_);
+ EXPECT_EQ(ResponseClassifier::NXRRSET,
+ ResponseClassifier::classify(qu_in_a_www, msg_a, cname_target,
+ cname_count));
+
+}
+
// Check the case where we have a simple answer in the answer section. This
// occurs when the QNAME/QTYPE/QCLASS matches one of the RRsets in the
// answer section - expect when the QTYPE is ANY, in which case the match
diff --git a/src/lib/server_common/tests/Makefile.am b/src/lib/server_common/tests/Makefile.am
index 560398f..55ccc85 100644
--- a/src/lib/server_common/tests/Makefile.am
+++ b/src/lib/server_common/tests/Makefile.am
@@ -36,6 +36,7 @@ run_unittests_LDADD += $(top_builddir)/src/lib/server_common/libserver_common.la
run_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libexceptions.la
run_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libasiolink.la
run_unittests_LDADD += $(top_builddir)/src/lib/cc/libcc.la
+run_unittests_LDADD += $(top_builddir)/src/lib/dns/libdns++.la
endif
noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/testutils/Makefile.am b/src/lib/testutils/Makefile.am
index e5cb46b..ae5c6da 100644
--- a/src/lib/testutils/Makefile.am
+++ b/src/lib/testutils/Makefile.am
@@ -11,6 +11,7 @@ libtestutils_la_SOURCES = srv_test.h srv_test.cc
libtestutils_la_SOURCES += dnsmessage_test.h dnsmessage_test.cc
libtestutils_la_SOURCES += mockups.h
libtestutils_la_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+libtestutils_la_LIBADD = $(top_builddir)/src/lib/asiolink/libasiolink.la
endif
EXTRA_DIST = portconfig.h
diff --git a/src/lib/testutils/srv_test.cc b/src/lib/testutils/srv_test.cc
index 4fec4ca..d5da8a0 100644
--- a/src/lib/testutils/srv_test.cc
+++ b/src/lib/testutils/srv_test.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 <netinet/in.h>
#include <dns/message.h>
diff --git a/src/lib/util/Makefile.am b/src/lib/util/Makefile.am
new file mode 100644
index 0000000..3e74708
--- /dev/null
+++ b/src/lib/util/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = io unittests io/tests
+# The io/tests is hack, because otherwise we can not order these directories
+# properly. Unittests use io and io/tests use unittest.
diff --git a/src/lib/util/io/Makefile.am b/src/lib/util/io/Makefile.am
new file mode 100644
index 0000000..b2653d8
--- /dev/null
+++ b/src/lib/util/io/Makefile.am
@@ -0,0 +1,16 @@
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+lib_LTLIBRARIES = libutil_io.la
+libutil_io_la_SOURCES = fd.h fd.cc fd_share.h fd_share.cc
+libutil_io_la_CXXFLAGS = $(AM_CXXFLAGS) -fno-strict-aliasing
+
+CLEANFILES = *.gcno *.gcda
+
+pyexec_LTLIBRARIES = libutil_io_python.la
+# Python prefers .so, while some OSes (specifically MacOS) use a different
+# suffix for dynamic objects. -module is necessary to work this around.
+libutil_io_python_la_LDFLAGS = -module
+libutil_io_python_la_SOURCES = fdshare_python.cc
+libutil_io_python_la_LIBADD = libutil_io.la
+libutil_io_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
+libutil_io_python_la_CXXFLAGS = $(AM_CXXFLAGS)
diff --git a/src/lib/util/io/fd.cc b/src/lib/util/io/fd.cc
new file mode 100644
index 0000000..04e64dc
--- /dev/null
+++ b/src/lib/util/io/fd.cc
@@ -0,0 +1,70 @@
+// 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 "fd.h"
+
+#include <unistd.h>
+#include <cerrno>
+
+namespace isc {
+namespace util {
+namespace io {
+
+bool
+write_data(const int fd, const void *buffer_v, const size_t length) {
+ const unsigned char *buffer(static_cast<const unsigned char *>(buffer_v));
+ size_t rest(length);
+ // Just keep writing until all is written
+ while (rest) {
+ ssize_t written(write(fd, buffer, rest));
+ if (rest == -1) {
+ if (errno == EINTR) { // Just keep going
+ continue;
+ } else {
+ return false;
+ }
+ } else { // Wrote something
+ rest -= written;
+ buffer += written;
+ }
+ }
+ return true;
+}
+
+ssize_t
+read_data(const int fd, void *buffer_v, const size_t length) {
+ unsigned char *buffer(static_cast<unsigned char *>(buffer_v));
+ size_t rest(length), already(0);
+ while (rest) { // Stil something to read
+ ssize_t amount(read(fd, buffer, rest));
+ if (rest == -1) {
+ if (errno == EINTR) { // Continue on interrupted call
+ continue;
+ } else {
+ return -1;
+ }
+ } else if (amount) {
+ already += amount;
+ rest -= amount;
+ buffer += amount;
+ } else { // EOF
+ return already;
+ }
+ }
+ return already;
+}
+
+}
+}
+}
diff --git a/src/lib/util/io/fd.h b/src/lib/util/io/fd.h
new file mode 100644
index 0000000..bdd2d41
--- /dev/null
+++ b/src/lib/util/io/fd.h
@@ -0,0 +1,61 @@
+// 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 __UTIL_IO_FD_H
+#define __UTIL_IO_FD_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fd.h
+ * @short Wrappers around common unix fd manipulation functions.
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+/*
+ * \short write() that writes everything.
+ * Wrapper around write(). The difference is, it never writes less data
+ * and looks successfull (eg. it blocks until all data are written).
+ * Retries on signals.
+ *
+ * \return True if sucessfull, false otherwise. The errno variable is left
+ * intact.
+ * \param fd Where to write.
+ * \param data The buffer to write.
+ * \param length How much data is there to write.
+ */
+bool
+write_data(const int fd, const void *data, const size_t length);
+
+/*
+ * \short read() that reads everything.
+ * Wrapper around read(). It does not do short reads, if it returns less,
+ * it means there was EOF. It retries on signals.
+ *
+ * \return Number of bytes read or -1 on error.
+ * \param fd Where to read data from.
+ * \param data Where to put the data.
+ * \param length How many of them.
+ */
+ssize_t
+read_data(const int fd, void *buffer, const size_t length);
+
+}
+}
+}
+
+#endif // __UTIL_IO_FD_H
diff --git a/src/lib/util/io/fd_share.cc b/src/lib/util/io/fd_share.cc
new file mode 100644
index 0000000..92576e0
--- /dev/null
+++ b/src/lib/util/io/fd_share.cc
@@ -0,0 +1,139 @@
+// Copyright (C) 2010 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 <cstring>
+#include <cstdlib>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <stdlib.h> // for malloc and free
+#include "fd_share.h"
+
+namespace isc {
+namespace util {
+namespace io {
+
+namespace {
+// Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
+// In order to ensure as much portability as possible, we provide wrapper
+// functions of these macros.
+// Note that cmsg_space() could run slow on OSes that do not have
+// CMSG_SPACE.
+inline socklen_t
+cmsg_len(const socklen_t len) {
+#ifdef CMSG_LEN
+ return (CMSG_LEN(len));
+#else
+ // Cast NULL so that any pointer arithmetic performed by CMSG_DATA
+ // is correct.
+ const uintptr_t hdrlen = (uintptr_t)CMSG_DATA(((struct cmsghdr*)NULL));
+ return (hdrlen + len);
+#endif
+}
+
+inline socklen_t
+cmsg_space(const socklen_t len) {
+#ifdef CMSG_SPACE
+ return (CMSG_SPACE(len));
+#else
+ struct msghdr msg;
+ struct cmsghdr* cmsgp;
+ // XXX: The buffer length is an ad hoc value, but should be enough
+ // in a practical sense.
+ char dummybuf[sizeof(struct cmsghdr) + 1024];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_control = dummybuf;
+ msg.msg_controllen = sizeof(dummybuf);
+
+ cmsgp = (struct cmsghdr*)dummybuf;
+ cmsgp->cmsg_len = cmsg_len(len);
+
+ cmsgp = CMSG_NXTHDR(&msg, cmsgp);
+ if (cmsgp != NULL) {
+ return ((char*)cmsgp - (char*)msg.msg_control);
+ } else {
+ return (0);
+ }
+#endif // CMSG_SPACE
+}
+}
+
+int
+recv_fd(const int sock) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_OTHER_ERROR);
+ }
+
+ if (recvmsg(sock, &msghdr, 0) < 0) {
+ free(msghdr.msg_control);
+ return (FD_COMM_ERROR);
+ }
+ const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ int fd = FD_OTHER_ERROR;
+ if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
+ cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
+ fd = *(const int*)CMSG_DATA(cmsg);
+ }
+ free(msghdr.msg_control);
+ return (fd);
+}
+
+int
+send_fd(const int sock, const int fd) {
+ struct msghdr msghdr;
+ struct iovec iov_dummy;
+ unsigned char dummy_data = 0;
+
+ iov_dummy.iov_base = &dummy_data;
+ iov_dummy.iov_len = sizeof(dummy_data);
+ msghdr.msg_name = NULL;
+ msghdr.msg_namelen = 0;
+ msghdr.msg_iov = &iov_dummy;
+ msghdr.msg_iovlen = 1;
+ msghdr.msg_flags = 0;
+ msghdr.msg_controllen = cmsg_space(sizeof(int));
+ msghdr.msg_control = malloc(msghdr.msg_controllen);
+ if (msghdr.msg_control == NULL) {
+ return (FD_OTHER_ERROR);
+ }
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
+ cmsg->cmsg_len = cmsg_len(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ *(int*)CMSG_DATA(cmsg) = fd;
+
+ const int ret = sendmsg(sock, &msghdr, 0);
+ free(msghdr.msg_control);
+ return (ret >= 0 ? 0 : FD_COMM_ERROR);
+}
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
diff --git a/src/lib/util/io/fd_share.h b/src/lib/util/io/fd_share.h
new file mode 100644
index 0000000..b74d4ef
--- /dev/null
+++ b/src/lib/util/io/fd_share.h
@@ -0,0 +1,65 @@
+// 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 FD_SHARE_H_
+#define FD_SHARE_H_
+
+/**
+ * \file fd_share.h
+ * \short Support to transfer file descriptors between processes.
+ * \todo This interface is very C-ish. Should we have some kind of exceptions?
+ */
+
+namespace isc {
+namespace util {
+namespace io {
+
+const int FD_COMM_ERROR = -2;
+const int FD_OTHER_ERROR = -1;
+
+/**
+ * \short Receives a file descriptor.
+ * This receives a file descriptor sent over an unix domain socket. This
+ * is the counterpart of send_fd().
+ *
+ * \return FD_COMM_ERROR when there's error receiving the socket, FD_OTHER_ERROR
+ * when there's a different error.
+ * \param sock The unix domain socket to read from. Tested and it does
+ * not work with a pipe.
+ */
+int recv_fd(const int sock);
+
+/**
+ * \short Sends a file descriptor.
+ * This sends a file descriptor over an unix domain socket. This is the
+ * counterpart of recv_fd().
+ *
+ * \return FD_COMM_ERROR when there's error sending the socket, FD_OTHER_ERROR
+ * for all other possible errors.
+ * \param sock The unix domain socket to send to. Tested and it does not
+ * work with a pipe.
+ * \param fd The file descriptor to send. It should work with any valid
+ * file descriptor.
+ */
+int send_fd(const int sock, const int fd);
+
+} // End for namespace io
+} // End for namespace util
+} // End for namespace isc
+
+#endif
+
+// Local Variables:
+// mode: c++
+// End:
diff --git a/src/lib/util/io/fdshare_python.cc b/src/lib/util/io/fdshare_python.cc
new file mode 100644
index 0000000..0a41107
--- /dev/null
+++ b/src/lib/util/io/fdshare_python.cc
@@ -0,0 +1,97 @@
+// Copyright (C) 2010 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.
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <structmember.h>
+
+#include <config.h>
+
+#include "fd_share.h"
+
+
+static PyObject*
+fdshare_recv_fd(PyObject*, PyObject* args) {
+ int sock, fd;
+ if (!PyArg_ParseTuple(args, "i", &sock)) {
+ return (NULL);
+ }
+ fd = isc::util::io::recv_fd(sock);
+ return (Py_BuildValue("i", fd));
+}
+
+static PyObject*
+fdshare_send_fd(PyObject*, PyObject* args) {
+ int sock, fd, result;
+ if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
+ return (NULL);
+ }
+ result = isc::util::io::send_fd(sock, fd);
+ return (Py_BuildValue("i", result));
+}
+
+static PyMethodDef fdshare_Methods[] = {
+ {"send_fd", fdshare_send_fd, METH_VARARGS, ""},
+ {"recv_fd", fdshare_recv_fd, METH_VARARGS, ""},
+ {NULL, NULL, 0, NULL} /* Sentinel */
+};
+
+
+static PyModuleDef bind10_fdshare_python = {
+ { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
+ "bind10_fdshare",
+ "Python bindings for fdshare",
+ -1,
+ fdshare_Methods,
+ NULL,
+ NULL,
+ NULL,
+ NULL
+};
+
+PyMODINIT_FUNC
+PyInit_libutil_io_python(void) {
+ PyObject *mod = PyModule_Create(&bind10_fdshare_python);
+ if (mod == NULL) {
+ return (NULL);
+ }
+
+ PyObject* FD_COMM_ERROR = Py_BuildValue("i", isc::util::io::FD_COMM_ERROR);
+ if (FD_COMM_ERROR == NULL) {
+ Py_XDECREF(mod);
+ return (NULL);
+ }
+ int ret = PyModule_AddObject(mod, "FD_COMM_ERROR", FD_COMM_ERROR);
+ if (-1 == ret) {
+ Py_XDECREF(FD_COMM_ERROR);
+ Py_XDECREF(mod);
+ return (NULL);
+ }
+
+ PyObject* FD_OTHER_ERROR = Py_BuildValue("i",
+ isc::util::io::FD_OTHER_ERROR);
+ if (FD_OTHER_ERROR == NULL) {
+ Py_XDECREF(mod);
+ return (NULL);
+ }
+ ret = PyModule_AddObject(mod, "FD_OTHER_ERROR", FD_OTHER_ERROR);
+ if (-1 == ret) {
+ Py_XDECREF(FD_OTHER_ERROR);
+ Py_XDECREF(mod);
+ return (NULL);
+ }
+
+ return (mod);
+}
+
diff --git a/src/lib/util/io/tests/Makefile.am b/src/lib/util/io/tests/Makefile.am
new file mode 100644
index 0000000..56d50cf
--- /dev/null
+++ b/src/lib/util/io/tests/Makefile.am
@@ -0,0 +1,25 @@
+CLEANFILES = *.gcno *.gcda
+
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+if USE_STATIC_LINK
+AM_LDFLAGS = -static
+endif
+
+TESTS =
+if HAVE_GTEST
+TESTS += run_unittests
+run_unittests_SOURCES = run_unittests.cc
+run_unittests_SOURCES += fd_tests.cc
+run_unittests_SOURCES += fd_share_tests.cc
+
+run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
+run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
+run_unittests_LDADD = $(GTEST_LDADD)
+run_unittests_LDADD += $(top_builddir)/src/lib/util/io/libutil_io.la
+run_unittests_LDADD += \
+ $(top_builddir)/src/lib/util/unittests/libutil_unittests.la
+endif
+
+noinst_PROGRAMS = $(TESTS)
diff --git a/src/lib/util/io/tests/fd_share_tests.cc b/src/lib/util/io/tests/fd_share_tests.cc
new file mode 100644
index 0000000..0902ce0
--- /dev/null
+++ b/src/lib/util/io/tests/fd_share_tests.cc
@@ -0,0 +1,74 @@
+// 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 "../fd.h"
+#include "../fd_share.h"
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <cstdio>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// We test that we can transfer a pipe over other pipe
+TEST(FDShare, transfer) {
+ // Get a pipe and fork
+ int pipes[2];
+ ASSERT_NE(-1, socketpair(AF_UNIX, SOCK_STREAM, 0, pipes));
+ pid_t sender(fork());
+ ASSERT_NE(-1, sender);
+ if(sender) { // We are in parent
+ // Close the other side of pipe, we want only writible one
+ EXPECT_NE(-1, close(pipes[0]));
+ // Get a process to check data
+ int fd(0);
+ pid_t checker(check_output(&fd, "data", 4));
+ ASSERT_NE(-1, checker);
+ // Now, send the file descriptor, close it and close the pipe
+ EXPECT_NE(-1, send_fd(pipes[1], fd));
+ EXPECT_NE(-1, close(pipes[1]));
+ EXPECT_NE(-1, close(fd));
+ // Check both subprocesses ended well
+ EXPECT_TRUE(process_ok(sender));
+ EXPECT_TRUE(process_ok(checker));
+ } else { // We are in child. We do not use ASSERT here
+ // Close the write end, we only read
+ if(close(pipes[1])) {
+ exit(1);
+ }
+ // Get the file descriptor
+ int fd(recv_fd(pipes[0]));
+ if(fd == -1) {
+ exit(1);
+ }
+ // This pipe is not needed
+ if(close(pipes[0])) {
+ exit(1);
+ }
+ // Send "data" trough the received fd, close it and be done
+ if(!write_data(fd, "data", 4) || close(fd) == -1) {
+ exit(1);
+ }
+ exit(0);
+ }
+}
+
+}
diff --git a/src/lib/util/io/tests/fd_tests.cc b/src/lib/util/io/tests/fd_tests.cc
new file mode 100644
index 0000000..12b70d8
--- /dev/null
+++ b/src/lib/util/io/tests/fd_tests.cc
@@ -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.
+
+#include "../fd.h"
+
+#include <util/unittests/fork.h>
+
+#include <gtest/gtest.h>
+
+using namespace isc::util::io;
+using namespace isc::util::unittests;
+
+namespace {
+
+// Make sure the test is large enough and does not fit into one
+// read or write request
+const size_t TEST_DATA_SIZE = 8 * 1024 * 1024;
+
+class FDTest : public ::testing::Test {
+ public:
+ unsigned char *data, *buffer;
+ FDTest() :
+ // We do not care what is inside, we just need it to be the same
+ data(new unsigned char[TEST_DATA_SIZE]),
+ buffer(NULL)
+ { }
+ ~ FDTest() {
+ delete[] data;
+ delete[] buffer;
+ }
+};
+
+// Test we read what was sent
+TEST_F(FDTest, read) {
+ int read_pipe(0);
+ buffer = new unsigned char[TEST_DATA_SIZE];
+ pid_t feeder(provide_input(&read_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(feeder, 0);
+ ssize_t received(read_data(read_pipe, buffer, TEST_DATA_SIZE));
+ EXPECT_TRUE(process_ok(feeder));
+ EXPECT_EQ(TEST_DATA_SIZE, received);
+ EXPECT_EQ(0, memcmp(data, buffer, received));
+}
+
+// Test we write the correct thing
+TEST_F(FDTest, write) {
+ int write_pipe(0);
+ pid_t checker(check_output(&write_pipe, data, TEST_DATA_SIZE));
+ ASSERT_GE(checker, 0);
+ EXPECT_TRUE(write_data(write_pipe, data, TEST_DATA_SIZE));
+ EXPECT_EQ(0, close(write_pipe));
+ EXPECT_TRUE(process_ok(checker));
+}
+
+}
diff --git a/src/lib/util/io/tests/run_unittests.cc b/src/lib/util/io/tests/run_unittests.cc
new file mode 100644
index 0000000..e787ab1
--- /dev/null
+++ b/src/lib/util/io/tests/run_unittests.cc
@@ -0,0 +1,22 @@
+// 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>
+
+int
+main(int argc, char *argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/src/lib/util/unittests/Makefile.am b/src/lib/util/unittests/Makefile.am
new file mode 100644
index 0000000..0ea8653
--- /dev/null
+++ b/src/lib/util/unittests/Makefile.am
@@ -0,0 +1,9 @@
+AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
+AM_CXXFLAGS = $(B10_CXXFLAGS)
+
+lib_LTLIBRARIES = libutil_unittests.la
+libutil_unittests_la_SOURCES = fork.h fork.cc
+libutil_unittests_la_LIBADD = \
+ $(top_builddir)/src/lib/util/io/libutil_io.la
+
+CLEANFILES = *.gcno *.gcda
diff --git a/src/lib/util/unittests/README b/src/lib/util/unittests/README
new file mode 100644
index 0000000..0ed888f
--- /dev/null
+++ b/src/lib/util/unittests/README
@@ -0,0 +1,5 @@
+This directory contains some code that is useful while writing various
+unittest code. It doesn't contain any code that would actually run in
+bind10.
+
+Because this is a test code, we do not test it explicitly.
diff --git a/src/lib/util/unittests/fork.cc b/src/lib/util/unittests/fork.cc
new file mode 100644
index 0000000..3414a3c
--- /dev/null
+++ b/src/lib/util/unittests/fork.cc
@@ -0,0 +1,145 @@
+// 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 "fork.h"
+
+#include <util/io/fd.h>
+
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+#include <cerrno>
+#include <stdlib.h>
+#include <stdio.h>
+
+using namespace isc::util::io;
+
+namespace {
+
+// Just a NOP function to ignore a signal but let it interrupt function.
+void no_handler(int) { }
+
+};
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+bool
+process_ok(pid_t process) {
+ // Create a timeout
+ struct sigaction ignored, original;
+ memset(&ignored, 0, sizeof ignored);
+ ignored.sa_handler = no_handler;
+ if (sigaction(SIGALRM, &ignored, &original)) {
+ return false;
+ }
+ // It is long, but if everything is OK, it'll not happen
+ alarm(10);
+ int status;
+ int result(waitpid(process, &status, 0) == -1);
+ // Cancel the alarm and return the original handler
+ alarm(0);
+ if (sigaction(SIGALRM, &original, NULL)) {
+ return false;
+ }
+ // Check what we found out
+ if (result) {
+ if (errno == EINTR)
+ kill(process, SIGTERM);
+ return false;
+ }
+ return WIFEXITED(status) && WEXITSTATUS(status) == 0;
+}
+
+/*
+ * This creates a pipe, forks and feeds the pipe with given data.
+ * Used to provide the input in non-blocking/asynchronous way.
+ */
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *read_pipe = pipes[0];
+ pid_t pid(fork());
+ if (pid) { // We are in the parent
+ return pid;
+ } else { // This is in the child, just puth the data there
+ close(pipes[0]);
+ if (!write_data(pipes[1], input, length)) {
+ exit(1);
+ } else {
+ close(pipes[1]);
+ exit(0);
+ }
+ }
+}
+
+/*
+ * This creates a pipe, forks and reads the pipe and compares it
+ * with given data. Used to check output of run in asynchronous way.
+ */
+pid_t
+check_output(int *write_pipe, const void *output, const size_t length)
+{
+ int pipes[2];
+ if (pipe(pipes)) {
+ return -1;
+ }
+ *write_pipe = pipes[1];
+ pid_t pid(fork());
+ if (pid) { // We are in parent
+ close(pipes[0]);
+ return pid;
+ } else {
+ close(pipes[1]);
+ // We don't return the memory, but we're in tests and end this process
+ // right away.
+ unsigned char *buffer = new unsigned char[length + 1];
+ // Try to read one byte more to see if the output ends here
+ size_t got_length(read_data(pipes[0], buffer, length + 1));
+ bool ok(true);
+ if (got_length != length) {
+ fprintf(stderr, "Different length (expected %u, got %u)\n",
+ static_cast<unsigned>(length),
+ static_cast<unsigned>(got_length));
+ ok = false;
+ }
+ if(!ok || memcmp(buffer, output, length)) {
+ const unsigned char *output_c(static_cast<const unsigned char *>(
+ output));
+ // If they differ, print what we have
+ for(size_t i(0); i != got_length; ++ i) {
+ fprintf(stderr, "%02hhx", buffer[i]);
+ }
+ fprintf(stderr, "\n");
+ for(size_t i(0); i != length; ++ i) {
+ fprintf(stderr, "%02hhx", output_c[i]);
+ }
+ fprintf(stderr, "\n");
+ exit(1);
+ } else {
+ exit(0);
+ }
+ }
+}
+
+}
+}
+}
diff --git a/src/lib/util/unittests/fork.h b/src/lib/util/unittests/fork.h
new file mode 100644
index 0000000..3331cfa
--- /dev/null
+++ b/src/lib/util/unittests/fork.h
@@ -0,0 +1,52 @@
+// 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 __UTIL_UNITTESTS_FORK_H
+#define __UTIL_UNITTESTS_FORK_H 1
+
+#include <unistd.h>
+
+/**
+ * @file fork.h
+ * @short Help functions to fork the test case process.
+ * Various functions to fork a process and feed some data to pipe, check
+ * its output and such lives here.
+ */
+
+namespace isc {
+namespace util {
+namespace unittests {
+
+/**
+ * @short Checks that a process terminates correctly.
+ * Waits for a process to terminate (with a short timeout, this should be
+ * used whan the process is about tu terminate) and checks its exit code.
+ *
+ * @return True if the process terminates with 0, false otherwise.
+ * @param process The ID of process to wait for.
+ */
+bool
+process_ok(pid_t process);
+
+pid_t
+provide_input(int *read_pipe, const void *input, const size_t length);
+
+pid_t
+check_output(int *write_pipe, const void *output, const size_t length);
+
+} // End of the namespace
+}
+}
+
+#endif // __UTIL_UNITTESTS_FORK_H
diff --git a/src/lib/xfr/Makefile.am b/src/lib/xfr/Makefile.am
index 79cee73..d714990 100644
--- a/src/lib/xfr/Makefile.am
+++ b/src/lib/xfr/Makefile.am
@@ -2,7 +2,7 @@ AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_srcdir)/src/lib/dns -I$(top_builddir)/src/lib/dns
AM_CPPFLAGS += $(BOOST_INCLUDES)
-AM_CXXFLAGS = $(B10_CXXFLAGS) -Wno-strict-aliasing
+AM_CXXFLAGS = $(B10_CXXFLAGS)
AM_CXXFLAGS += -Wno-unused-parameter # see src/lib/cc/Makefile.am
if USE_CLANGPP
AM_CXXFLAGS += -Wno-error
@@ -11,13 +11,5 @@ endif
CLEANFILES = *.gcno *.gcda
lib_LTLIBRARIES = libxfr.la
-libxfr_la_SOURCES = xfrout_client.h xfrout_client.cc
-libxfr_la_SOURCES += fd_share.h fd_share.cc
-
-pyexec_LTLIBRARIES = libxfr_python.la
-# Python prefers .so, while some OSes (specifically MacOS) use a different
-# suffix for dynamic objects. -module is necessary to work this around.
-libxfr_python_la_LDFLAGS = -module
-libxfr_python_la_SOURCES = fdshare_python.cc fd_share.cc fd_share.h
-libxfr_python_la_CPPFLAGS = $(AM_CPPFLAGS) $(PYTHON_INCLUDES)
-libxfr_python_la_CXXFLAGS = $(AM_CXXFLAGS)
+libxfr_la_SOURCES = xfrout_client.h xfrout_client.cc
+libxfr_la_LIBADD = $(top_builddir)/src/lib/util/io/libutil_io.la
diff --git a/src/lib/xfr/fd_share.cc b/src/lib/xfr/fd_share.cc
deleted file mode 100644
index 4e1f093..0000000
--- a/src/lib/xfr/fd_share.cc
+++ /dev/null
@@ -1,137 +0,0 @@
-// Copyright (C) 2010 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 <cstring>
-#include <cstdlib>
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/uio.h>
-#include <stdlib.h> // for malloc and free
-#include <xfr/fd_share.h>
-
-namespace isc {
-namespace xfr {
-
-namespace {
-// Not all OSes support advanced CMSG macros: CMSG_LEN and CMSG_SPACE.
-// In order to ensure as much portability as possible, we provide wrapper
-// functions of these macros.
-// Note that cmsg_space() could run slow on OSes that do not have
-// CMSG_SPACE.
-inline socklen_t
-cmsg_len(const socklen_t len) {
-#ifdef CMSG_LEN
- return (CMSG_LEN(len));
-#else
- // Cast NULL so that any pointer arithmetic performed by CMSG_DATA
- // is correct.
- const uintptr_t hdrlen = (uintptr_t)CMSG_DATA(((struct cmsghdr*)NULL));
- return (hdrlen + len);
-#endif
-}
-
-inline socklen_t
-cmsg_space(const socklen_t len) {
-#ifdef CMSG_SPACE
- return (CMSG_SPACE(len));
-#else
- struct msghdr msg;
- struct cmsghdr* cmsgp;
- // XXX: The buffer length is an ad hoc value, but should be enough
- // in a practical sense.
- char dummybuf[sizeof(struct cmsghdr) + 1024];
-
- memset(&msg, 0, sizeof(msg));
- msg.msg_control = dummybuf;
- msg.msg_controllen = sizeof(dummybuf);
-
- cmsgp = (struct cmsghdr*)dummybuf;
- cmsgp->cmsg_len = cmsg_len(len);
-
- cmsgp = CMSG_NXTHDR(&msg, cmsgp);
- if (cmsgp != NULL) {
- return ((char*)cmsgp - (char*)msg.msg_control);
- } else {
- return (0);
- }
-#endif // CMSG_SPACE
-}
-}
-
-int
-recv_fd(const int sock) {
- struct msghdr msghdr;
- struct iovec iov_dummy;
- unsigned char dummy_data;
-
- iov_dummy.iov_base = &dummy_data;
- iov_dummy.iov_len = sizeof(dummy_data);
- msghdr.msg_name = NULL;
- msghdr.msg_namelen = 0;
- msghdr.msg_iov = &iov_dummy;
- msghdr.msg_iovlen = 1;
- msghdr.msg_flags = 0;
- msghdr.msg_controllen = cmsg_space(sizeof(int));
- msghdr.msg_control = malloc(msghdr.msg_controllen);
- if (msghdr.msg_control == NULL) {
- return (-1);
- }
-
- if (recvmsg(sock, &msghdr, 0) < 0) {
- free(msghdr.msg_control);
- return (XFR_FD_RECEIVE_FAIL);
- }
- const struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
- int fd = -1;
- if (cmsg != NULL && cmsg->cmsg_len == cmsg_len(sizeof(int)) &&
- cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
- fd = *(const int*)CMSG_DATA(cmsg);
- }
- free(msghdr.msg_control);
- return (fd);
-}
-
-int
-send_fd(const int sock, const int fd) {
- struct msghdr msghdr;
- struct iovec iov_dummy;
- unsigned char dummy_data = 0;
-
- iov_dummy.iov_base = &dummy_data;
- iov_dummy.iov_len = sizeof(dummy_data);
- msghdr.msg_name = NULL;
- msghdr.msg_namelen = 0;
- msghdr.msg_iov = &iov_dummy;
- msghdr.msg_iovlen = 1;
- msghdr.msg_flags = 0;
- msghdr.msg_controllen = cmsg_space(sizeof(int));
- msghdr.msg_control = malloc(msghdr.msg_controllen);
- if (msghdr.msg_control == NULL) {
- return (-1);
- }
-
- struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msghdr);
- cmsg->cmsg_len = cmsg_len(sizeof(int));
- cmsg->cmsg_level = SOL_SOCKET;
- cmsg->cmsg_type = SCM_RIGHTS;
- *(int*)CMSG_DATA(cmsg) = fd;
-
- const int ret = sendmsg(sock, &msghdr, 0);
- free(msghdr.msg_control);
- return (ret >= 0 ? 0 : -1);
-}
-
-} // End for namespace xfr
-} // End for namespace isc
diff --git a/src/lib/xfr/fd_share.h b/src/lib/xfr/fd_share.h
deleted file mode 100644
index 4ee5fd5..0000000
--- a/src/lib/xfr/fd_share.h
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright (C) 2010 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 FD_SHARE_H_
-#define FD_SHARE_H_
-
-namespace isc {
-namespace xfr {
-
-/// Failed to receive xfr socket descriptor "fd" on unix domain socket 'sock'
-const int XFR_FD_RECEIVE_FAIL = -2;
-
-// Receive socket descriptor on unix domain socket 'sock'.
-// Returned value is the socket descriptor received.
-// Returned XFR_FD_RECEIVE_FAIL if failed to receive xfr socket descriptor
-// Errors are indicated by a return value of -1.
-int recv_fd(const int sock);
-
-// Send socket descriptor "fd" to server over unix domain socket 'sock',
-// the connection from socket 'sock' to unix domain server should be established first.
-// Errors are indicated by a return value of -1.
-int send_fd(const int sock, const int fd);
-
-} // End for namespace xfr
-} // End for namespace isc
-
-#endif
-
-// Local Variables:
-// mode: c++
-// End:
diff --git a/src/lib/xfr/fdshare_python.cc b/src/lib/xfr/fdshare_python.cc
deleted file mode 100644
index 82b1b6e..0000000
--- a/src/lib/xfr/fdshare_python.cc
+++ /dev/null
@@ -1,84 +0,0 @@
-// Copyright (C) 2010 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.
-
-#define PY_SSIZE_T_CLEAN
-#include <Python.h>
-#include <structmember.h>
-
-#include <config.h>
-
-#include <xfr/fd_share.h>
-
-
-static PyObject*
-fdshare_recv_fd(PyObject*, PyObject* args) {
- int sock, fd;
- if (!PyArg_ParseTuple(args, "i", &sock)) {
- return (NULL);
- }
- fd = isc::xfr::recv_fd(sock);
- return (Py_BuildValue("i", fd));
-}
-
-static PyObject*
-fdshare_send_fd(PyObject*, PyObject* args) {
- int sock, fd, result;
- if (!PyArg_ParseTuple(args, "ii", &sock, &fd)) {
- return (NULL);
- }
- result = isc::xfr::send_fd(sock, fd);
- return (Py_BuildValue("i", result));
-}
-
-static PyMethodDef fdshare_Methods[] = {
- {"send_fd", fdshare_send_fd, METH_VARARGS, ""},
- {"recv_fd", fdshare_recv_fd, METH_VARARGS, ""},
- {NULL, NULL, 0, NULL} /* Sentinel */
-};
-
-
-static PyModuleDef bind10_fdshare_python = {
- { PyObject_HEAD_INIT(NULL) NULL, 0, NULL},
- "bind10_fdshare",
- "Python bindings for fdshare",
- -1,
- fdshare_Methods,
- NULL,
- NULL,
- NULL,
- NULL
-};
-
-PyMODINIT_FUNC
-PyInit_libxfr_python(void) {
- PyObject* mod = PyModule_Create(&bind10_fdshare_python);
- if (mod == NULL) {
- return (NULL);
- }
-
- PyObject* XFR_FD_RECEIVE_FAIL = Py_BuildValue("i", isc::xfr::XFR_FD_RECEIVE_FAIL);
- if (XFR_FD_RECEIVE_FAIL == NULL) {
- Py_XDECREF(mod);
- return (NULL);
- }
- int ret = PyModule_AddObject(mod, "XFR_FD_RECEIVE_FAIL", XFR_FD_RECEIVE_FAIL);
- if (-1 == ret) {
- Py_XDECREF(XFR_FD_RECEIVE_FAIL);
- Py_XDECREF(mod);
- return (NULL);
- }
-
- return (mod);
-}
-
diff --git a/src/lib/xfr/python_xfr.cc b/src/lib/xfr/python_xfr.cc
deleted file mode 100644
index 52848ad..0000000
--- a/src/lib/xfr/python_xfr.cc
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright (C) 2010 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 <boost/python.hpp>
-#include <boost/python/class.hpp>
-#include <boost/python/module.hpp>
-#include <boost/python/def.hpp>
-#include <boost/python/exception_translator.hpp>
-#include <boost/python/return_internal_reference.hpp>
-#include <boost/python/copy_const_reference.hpp>
-#include <boost/shared_ptr.hpp>
-
-#include <xfr/fd_share.h>
-
-using namespace isc::xfr;
-using namespace boost::python;
-
-BOOST_PYTHON_MODULE(bind10_xfr) {
- def("recv_fd", &recv_fd);
- def("send_fd", &send_fd);
-}
diff --git a/src/lib/xfr/xfrout_client.cc b/src/lib/xfr/xfrout_client.cc
index e9e736b..6ab905b 100644
--- a/src/lib/xfr/xfrout_client.cc
+++ b/src/lib/xfr/xfrout_client.cc
@@ -20,10 +20,11 @@
#include <unistd.h>
#include <asio.hpp>
-#include <xfr/fd_share.h>
+#include <util/io/fd_share.h>
#include <xfr/xfrout_client.h>
using namespace std;
+using namespace isc::util::io;
using asio::local::stream_protocol;
namespace isc {
@@ -72,7 +73,7 @@ XfroutClient::sendXfroutRequestInfo(const int tcp_sock,
const void* const msg_data,
const uint16_t msg_len)
{
- if (-1 == send_fd(impl_->socket_.native(), tcp_sock)) {
+ if (send_fd(impl_->socket_.native(), tcp_sock) < 0) {
isc_throw(XfroutError,
"Failed to send the socket file descriptor "
"to xfrout module");
diff --git a/tests/system/bindctl/tests.sh b/tests/system/bindctl/tests.sh
index 354f349..6923c41 100755
--- a/tests/system/bindctl/tests.sh
+++ b/tests/system/bindctl/tests.sh
@@ -31,7 +31,7 @@ grep 192.0.2.1 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
-echo "I:Checking BIND 10 statistics after a pose ($n)"
+echo "I:Checking BIND 10 statistics after a pause ($n)"
# wait for 2sec to make sure b10-stats gets the latest statistics.
# note that we set statistics-interval to 1.
sleep 2
@@ -67,7 +67,7 @@ grep 192.0.2.1 dig.out.$n > /dev/null || status=1
if [ $status != 0 ]; then echo "I:failed"; fi
n=`expr $n + 1`
-echo "I:Rechecking BIND 10 statistics after a pose ($n)"
+echo "I:Rechecking BIND 10 statistics after a pause ($n)"
sleep 2
echo 'Stats show
' | $RUN_BINDCTL \
More information about the bind10-changes
mailing list